LATEST TOPICS

ONLINE statistics collection in Oracle Database 12c : Part 2

Today’s discussion is again on the ONLINE statistics collection in Oracle database 12c. In one of my earlier post, I had discussed about the extensions/enhancements made to the online statistics collections in Oracle 12c. We have seen that Oracle now gather the statistics automatically (ONLINE) for tables if it is being created with CTAS (Create Table As Select) statement. We have also seen which parameter controls this behaviour of online statistics collection and how we can bypass the online statistics collection by hinting the optimizer for specific SQLs.

However, today’s discussion is more focused on the restrictions and limitation associated with the online statistics collection in Oracle 12c.

Online statistics for Bulk Load (INSERT INTO..SELECT)

In the last article, we had just discussed about online statistics collection in the context of table creation with CTAS statement. Oracle also collects online statistics when bulk load (insert) is performed using INSERT INTO SELECT statement. However, there are certain restrictions associated with this type statistics collection.

Oracle collects online statistics for bulk load (INSERT INTO..SELECT) operations only when it is performed using direct path load method.

Lets, go through a quick example to check how the online statistics works with direct path insert.

----//
----// Creating a table for demonstration //----
----//
SQL> create table T_ONLINE_STATS
  2  (
  3  id number,
  4  name varchar(15),
  5  join_date date
  6  );

Table created.

----//
----// No segments are created for this table (due to the deferred segment behaviour) //----
----//
SQL> select table_name, segment_created from user_tables where table_name='T_ONLINE_STATS';

TABLE_NAME           SEG
-------------------- ---
T_ONLINE_STATS       NO

----//
----// No statistics available for this new table //----
----//
SQL> select TABLE_NAME,NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STALE_STATS
  2  from user_tab_statistics where table_name='T_ONLINE_STATS';

TABLE_NAME             NUM_ROWS SAMPLE_SIZE LAST_ANAL STA
-------------------- ---------- ----------- --------- ---
T_ONLINE_STATS

Now, lets perform a bulk load (direct path) into this table.

----//
----// performing direct path insert into the table //----
----//
SQL> insert /*+ APPEND */ into T_ONLINE_STATS
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=1000
  4  ;

1000 rows created.

SQL> commit;

Commit complete.

We have used the hint /*+ APPEND */ to perform the bulk load. The append hint performs the insert operation in direct path mode. We haven’t gathered any statistics for this new table yet.
Lets query the statistics against this table.

----//
----// querying statistics for the new table //----
----//
SQL> select TABLE_NAME,NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STALE_STATS
  2  from user_tab_statistics where table_name='T_ONLINE_STATS';

TABLE_NAME             NUM_ROWS SAMPLE_SIZE LAST_ANAL STA
-------------------- ---------- ----------- --------- ---
T_ONLINE_STATS             1000        1000 04-OCT-15 NO

As we could see Oracle automatically collected the statistics during the bulk load operation. If we query the individual column statistics from ALL/DBA/USER_TAB_COL_STATISTICS views, we can also see a note indicating the statistics were collected during the bulk load

----//
----// query individual column statistics //----
----//
SQL> select TABLE_NAME,COLUMN_NAME,LAST_ANALYZED,NOTES
  2  from user_tab_col_statistics where table_name='T_ONLINE_STATS';

TABLE_NAME           COLUMN_NAME     LAST_ANAL NOTES
-------------------- --------------- --------- ------------------------------
T_ONLINE_STATS       JOIN_DATE       04-OCT-15 STATS_ON_LOAD  ---> indicates online statistics collection
T_ONLINE_STATS       NAME            04-OCT-15 STATS_ON_LOAD
T_ONLINE_STATS       ID              04-OCT-15 STATS_ON_LOAD

Now comes the restriction. The same bulk load will not lead into online statistics collection (not completely true), if the table is not having empty segments (empty table). This means, if we are doing bulk load in a table which is already having data, Oracle will not perform online statistics collection.

Lets validate this theory by a quick example. I am using the same table from previous example without erasing the existing data to simulate a non-empty table.

----//
----// deleting existing statistics from the table //---
----//
SQL> exec dbms_stats.delete_table_stats ('MYAPP','T_ONLINE_STATS');

PL/SQL procedure successfully completed.

----//
----// Validating the table is not empty //---
----//
SQL>  select table_name, segment_created from user_tables where table_name='T_ONLINE_STATS';

TABLE_NAME           SEG
-------------------- ---
T_ONLINE_STATS       YES

----//
----// validate no statistics exist for the table //---
----//

SQL> select TABLE_NAME,NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STALE_STATS
  2  from user_tab_statistics where table_name='T_ONLINE_STATS';

TABLE_NAME             NUM_ROWS SAMPLE_SIZE LAST_ANAL STA
-------------------- ---------- ----------- --------- ---
T_ONLINE_STATS

I have just deleted the existing statistics from the table to validate whether Oracle would collect fresh statistics for a non-empty table. Now, lets perform a bulk load again (this time our table is not empty)

----//
----// performing direct path load into non empty table //---
----//

SQL> insert /*+ APPEND */ into T_ONLINE_STATS
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=1000
  4  ;

1000 rows created.

SQL> commit;

Commit complete.

Unlike in the case of empty table, Oracle did not collect statistics this time as our table was not empty as shown below.

----//
----// query table statistics after bulk load //---
----//

SQL> select TABLE_NAME,NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STALE_STATS
  2  from user_tab_statistics where table_name='T_ONLINE_STATS';

TABLE_NAME             NUM_ROWS SAMPLE_SIZE LAST_ANAL STA
-------------------- ---------- ----------- --------- ---
T_ONLINE_STATS

Although, we don’t see any statistics for our table; Oracle actually collected the statistics for the bulk load (and it will collect online statistics for every direct path load in 12c with default behaviour). It is just that, Oracle did not save the online statistics when it found the table to be non-empty.

We can check the execution plan of the bulk load operation to validate that Oracle actually collected online statistics.

SQL> explain plan for
  2  insert /*+ APPEND */ into T_ONLINE_STATS
  3  select rownum, rpad('X',15,'X'), sysdate
  4  from dual connect by rownum <=1000;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
Plan hash value: 1600317434

-------------------------------------------------------------------------------------------
| Id  | Operation                        | Name           | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                 |                |     1 |     2   (0)| 00:00:01 |
|   1 |  LOAD AS SELECT                  | T_ONLINE_STATS |       |            |          |
|   2 |   OPTIMIZER STATISTICS GATHERING |                |     1 |     2   (0)| 00:00:01 |
|   3 |    COUNT                         |                |       |            |          |
|*  4 |     CONNECT BY WITHOUT FILTERING |                |       |            |          |
|   5 |      FAST DUAL                   |                |     1 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter(ROWNUM<=1000)

17 rows selected.

As we can see from the execution plan, OPTIMIZER STATISTICS GATHERING is present in our plan which indicates online statistics was performed for the bulk load. Online statistics collection is a feature of the Oracle 12c optimizer and a non-empty segment doesn't (depends) effect that statistics collection. The statistics are ignored in a later phase if Oracle finds the table to be non-empty. However, there are some special cases where the optimizer avoid statistics collection depending on the table type (we will see those in coming sections)

We can also confirm this behaviour by enabling trace on DBMS_STATS as shown below.

When table is empty:

SQL> set serveroutput on
SQL> exec dbms_stats.set_global_prefs('TRACE',1+65536);

PL/SQL procedure successfully completed.

SQL> insert /*+ APPEND */ into T_ONLINE_STATS
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=1e3;
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 1         1         1         to_char(count("ID"))                                                   100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 2         1         1         substrb(dump(min("ID"),16,0,64),1,240)                                 9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 3         1         1         substrb(dump(max("ID"),16,0,64),1,240)                                 17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 4         2         2         to_char(count("NAME"))                                                 100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 5         2         2         substrb(dump(min("NAME"),16,0,64),1,240)                               9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 6         2         2         substrb(dump(max("NAME"),16,0,64),1,240)                               17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 7         3         3         to_char(count("JOIN_DATE"))                                            96
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 8         3         3         substrb(dump(min("JOIN_DATE"),16,0,64),1,240)                          9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 9         3         3         substrb(dump(max("JOIN_DATE"),16,0,64),1,240)                          17
DBMS_STATS: postprocess online optimizer stats gathering for MYAPP.T_ONLINE_STATS: save statistics
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 1         1          1000      1000      0         2891      1000
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 2         4          1000      1         0         15000     NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 3         7          1000      1         0         7000      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 4         2         Typ=2 Len=2: c1,2              NULL      NULL      NULL      NULL      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 5         3         Typ=2 Len=2: c2,b              NULL      NULL      NULL      NULL      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 6         5         Typ=1 Len=15: 58,58,58,58,58,5 NULL      NULL      NULL      NULL      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 7         6         Typ=1 Len=15: 58,58,58,58,58,5 NULL      NULL      NULL      NULL      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 8         8         Typ=12 Len=7: 78,73,a,7,e,26,1 NULL      NULL      NULL      NULL      NULL
DBMS_STATS: RAWIDX    SELMAPPOS RES                            NNV       NDV       SPLIT     RSIIZE    ROWCNT
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 9         9         Typ=12 Len=7: 78,73,a,7,e,26,1 NULL      NULL      NULL      NULL      NULL
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 1         1         1         to_char(count("ID"))                                                   100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 2         1         1         substrb(dump(min("ID"),16,0,64),1,240)                                 9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 3         1         1         substrb(dump(max("ID"),16,0,64),1,240)                                 17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 4         2         2         to_char(count("NAME"))                                                 100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 5         2         2         substrb(dump(min("NAME"),16,0,64),1,240)                               9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 6         2         2         substrb(dump(max("NAME"),16,0,64),1,240)                               17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 7         3         3         to_char(count("JOIN_DATE"))                                            96
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 8         3         3         substrb(dump(min("JOIN_DATE"),16,0,64),1,240)                          9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 9         3         3         substrb(dump(max("JOIN_DATE"),16,0,64),1,240)                          17

1000 rows created.

When table is not empty:

SQL> insert /*+ APPEND */ into T_ONLINE_STATS
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=9e5;
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 1         1         1         to_char(count("ID"))                                                   100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 2         1         1         substrb(dump(min("ID"),16,0,64),1,240)                                 9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 3         1         1         substrb(dump(max("ID"),16,0,64),1,240)                                 17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 4         2         2         to_char(count("NAME"))                                                 100
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 5         2         2         substrb(dump(min("NAME"),16,0,64),1,240)                               9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 6         2         2         substrb(dump(max("NAME"),16,0,64),1,240)                               17
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 7         3         3         to_char(count("JOIN_DATE"))                                            96
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 8         3         3         substrb(dump(min("JOIN_DATE"),16,0,64),1,240)                          9
DBMS_STATS: SELMAPPOS CLISTIDX  INTCOL    SELITEM                                                                GATHFLG
DBMS_STATS: ----------------------------------------------------------------------------------------------------
DBMS_STATS: 9         3         3         substrb(dump(max("JOIN_DATE"),16,0,64),1,240)                          17

900000 rows created.

As we can observe from the trace, when our table was empty, Oracle collected online stats for direct path load and then saved the statistics as a post process (DBMS_STATS: post process online optimizer stats gathering for MYAPP.T_ONLINE_STATS: save statistics). However, when the table was not empty it collected the stats but did not save it during the post process.

Online statistics for bulk load in partitioned tables

Oracle also collects online statistics for direct path bulk load against partitioned tables provided the target partition is empty. Lets validate with a quick example.

----//
----// creating a partitioned table for demonstration //----
----//
SQL> create table T_ONLINE_STATS_PART
  2  (
  3  id number,
  4  name varchar(15),
  5  join_date date
  6  )
  7  partition by range (join_date)
  8  (
  9  partition PART01 values less than (TO_DATE('01-OCT-2015','DD-MON-YYYY')) tablespace APPDATA,
 10  partition PART02 values less than (TO_DATE('01-NOV-2015','DD-MON-YYYY')) tablespace APPDATA
 11  );

Table created.

SQL>

----//
----// validating table partitions have empty segments //----
----//
SQL> select table_name,partition_name,segment_created,last_analyzed from user_tab_partitions where table_name='T_ONLINE_STATS_PART';

TABLE_NAME           PARTITION_NAME  SEGM LAST_ANAL
-------------------- --------------- ---- ---------
T_ONLINE_STATS_PART  PART01          NO
T_ONLINE_STATS_PART  PART02          NO

----//
----// table partitions do not have any statistics //----
----//
SQL> select table_name,partition_name,num_rows,last_analyzed
  2  from user_tab_statistics where table_name='T_ONLINE_STATS_PART';

TABLE_NAME           PARTITION_NAME    NUM_ROWS LAST_ANAL 
-------------------- --------------- ---------- --------- 
T_ONLINE_STATS_PART                                       
T_ONLINE_STATS_PART  PART01                               
T_ONLINE_STATS_PART  PART02                               

Lets perform a direct path bulk load into this partitioned table

----//
----// performing direct path load in partition table  //----
----//
SQL> insert /*+ APPEND */ into T_ONLINE_STATS_PART
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=100
  4  ;

100 rows created.

SQL> commit;

Commit complete.

We have loaded data in one of the partition (PART02). Lets see if Oracle has collected statistics during this direct path bulk load.

----//
----// validating statistics collection //----
----//

SQL> select table_name,partition_name,num_rows,last_analyzed
  2  from user_tab_statistics where table_name='T_ONLINE_STATS_PART';

TABLE_NAME           PARTITION_NAME    NUM_ROWS LAST_ANAL 
-------------------- --------------- ---------- --------- 
T_ONLINE_STATS_PART                         100 04-OCT-15 
T_ONLINE_STATS_PART  PART01                               
T_ONLINE_STATS_PART  PART02                               

As we can observe, Oracle did collect online statistics during the bulk load. However, it was collected at the table level (global statistics) and not at the partition level. This is another limitation of online statistics, where it collects only global statistics when direct path load is performed against a partitioned table.

We do have a option to overcome this limitation by explicitly referring the partition name during the direct path load. Lets validate that by doing a bulk load against the other empty partition (PART01)

----//
----// Validating the empty table partition //----
----//
SQL> select count(*) from T_ONLINE_STATS_PART partition(PART01);

  COUNT(*)
----------
         0

SQL> select count(*) from T_ONLINE_STATS_PART partition(PART02);

  COUNT(*)
----------
       100

----//	   
----// performing bulk load in specific table partition //----
----// 
SQL> insert /*+ APPEND */ into T_ONLINE_STATS_PART partition (PART01)
  2  select rownum, rpad('X',15,'X'), sysdate-20
  3  from dual connect by rownum <=100;

100 rows created.

SQL> commit;

Commit complete.

----//
----// record count after bulk laod //----
----//
SQL> select count(*) from T_ONLINE_STATS_PART partition(PART01);

  COUNT(*)
----------
       100

SQL> select count(*) from T_ONLINE_STATS_PART partition(PART02);

  COUNT(*)
----------
       100

Now, lets query if Oracle collected online statistics for the specific table partition

----//
----// validating table partition statistics //----
----//
SQL> select table_name,partition_name,num_rows,last_analyzed
  2  from user_tab_statistics where table_name='T_ONLINE_STATS_PART';

TABLE_NAME           PARTITION_NAME         NUM_ROWS LAST_ANAL
-------------------- -------------------- ---------- ---------
T_ONLINE_STATS_PART                              100 04-OCT-15
T_ONLINE_STATS_PART  PART01                      100 04-OCT-15
T_ONLINE_STATS_PART  PART02

As we can see, Oracle was able to collect partition level statistics this time (as we had explicitly referred the partition during bulk load). However, it did not update the global statistics this time.

Online statistics with Index Organized Table (IOT), External Tables and other types of tables

Oracle doesn't collect online statistics (during both CTAS or bulk load) if the target table is an IOT or external table. This is because, when we create a IOT or external table, the segment is pre-allocated to the tables which prevents Oracle to collect online statistics for this type of tables.

----//
----// creating an IOT for demonstration //----
----//
SQL> create table T_ONLINE_STATS_IOT
  2  (
  3  id number,
  4  name varchar(15),
  5  join_date date,
  6  constraint T_ONLINE_STATS_IOT_PK primary key (id)
  7  )
  8  organization index
  9  ;

Table created.


----//
----// segment (for index) is pre allocated to IOT //----
----//

SQL> select table_name, segment_created from user_tables where table_name='T_ONLINE_STATS_IOT';

TABLE_NAME           SEG
-------------------- ---
T_ONLINE_STATS_IOT   YES

Lets perform a bulk load and see whether Oracle collects online statistics for this IOT.

----//
----// performing bulk load in IOT //---
----//
SQL> insert /*+ APPEND */ into T_ONLINE_STATS_IOT
  2  select rownum, rpad('X',15,'X'), sysdate
  3  from dual connect by rownum <=100
  4  ;

100 rows created.

SQL> commit;

Commit complete.

SQL> select TABLE_NAME,NUM_ROWS,SAMPLE_SIZE,LAST_ANALYZED,STALE_STATS
  2  from user_tab_statistics where table_name='T_ONLINE_STATS_IOT';

TABLE_NAME             NUM_ROWS SAMPLE_SIZE LAST_ANAL STA
-------------------- ---------- ----------- --------- ---
T_ONLINE_STATS_IOT

I had mentioned earlier that online statistics collection is a optimizer feature and it always collects online statistics (rejects later if table is found non-empty) for direct path load. Well, here is the exception; it doesn't collect online statistics if it finds the target table is an IOT or external table or nested table or transaction specific temp table.

Looks like this pre-check is coded in optimizer and it bypasses online statistics collection when any of the above mentioned table type is encountered. If we review the optimizer trace (10053), we can see the following note about this pre-check.

#----//
#----// optimizer trace on bypassing online statistics collection //----
#----//
ONLINEST: Checking validity of online stats gathering
ONLINEST: Failed validity check: target table contains a nested table type, an IOT,  an external table or a transaction specific temp table.
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
Starting SQL statement dump

user_id=63 user_name=MYAPP module=SQL*Plus action=
sql_id=bt961us36tf84 plan_hash_value=1600317434 problem_type=0
----- Current SQL Statement for this session (sql_id=bt961us36tf84) -----
insert /*+ APPEND */ into T_ONLINE_STATS_IOT
select rownum, rpad('X',15,'X'), sysdate
from dual connect by rownum <=100

Here is another trace when we try to create an IOT with CTAS statement.

#----//
#----// optimizer trace on bypassing online statistics collection //----
#----//
ONLINEST: Checking validity of online stats gathering
ONLINEST: Failed validity check: target table contains a nested table type, an IOT,  an external table or a transaction specific temp table.
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
kkeCostToTime: using io calibrate stats maxpmbps=200(MB/s)
 block_size=8192 mb_io_count=1 mb_io_size=8192 (bytes)
 tot_io_size=0(MB) time=0(ms)
Starting SQL statement dump

user_id=63 user_name=MYAPP module=SQL*Plus action=
sql_id=g6pcx3dn32g79 plan_hash_value=571664813 problem_type=0
----- Current SQL Statement for this session (sql_id=g6pcx3dn32g79) -----
create table T_ONLINE_STATS_IOT
(
id,
name,
join_date,
constraint T_ONLINE_STATS_IOT_PK primary key (id)
)
organization index
AS
select * from T_ONLINE_STATS_PART partition(PART02)

There are few other restrictions that apply to online statistics collection for a Oracle table. For a complete list of restrictions, you can refer here

Franck Pachot has also discussed another undocumented restrictions about online statistics gathering in this blog post

Observations

Following are the observations that I made based on the experiments performed

  • Online statistics collection works only when the table or table partition is empty
  • Online statistics collection always trigger for direct path load with exception for IOT, external table, nested table and transaction specific temp tables
  • Online statistics collection decision is taken by optimizer
  • Online statistics collection by default collects only global statistics for partitioned table if the partition name is not explicitly referred in bulk load
  • Online statistics collection only gathers partition level statistics when a partition name is explicitly referred during bulk load
  • If global statistics already exists for a partitioned table, online statistics collection doesn't not update global statistics when bulk load is performed against partitioned table even if the underlying table partition is empty
  • Online statistics collection never overwrites existing table statistics

Conclusion

Online statistics collection for database table is targeted only for the bulk load operations to avoid manual statistics collection after bulk loads. This feature might be useful in scenarios where statistics collections are missed out after bulk loads leading to stale state and in turn unstable plans. However, the feature doesn't address all the bulk load operations. There is a pre-requisite to have empty table/partition for the feature to work, which is not practical in most of the real time database environments.

On the contrary, this feature may have a negative impact on other database DMLs where direct path inserts are used as the optimizer by default collects online statistics with this feature when direct path insert is involved.

It wouldn't be a good decision to rely and use the online statistics collection for tables considering the limitations and negative impact it can have on the database.

%d bloggers like this:
Visit Us On LinkedinVisit Us On TwitterVisit Us On Google PlusCheck Our Feed