This issue came through from an AskTom question, that turned out to be a known bug. In my talks on optimizer statistics, I’ve often mentioned the benefit of using pending statistics as a risk mitigation facility, and since this bug involved pending statistics, I thought I would bring it to your attention. The issue occurs when optimizing a query that accesses a single partition via pending statistics.
SQL> create table t
2 partition by range(x)(
3 partition p1 values less than (2),
4 partition p2 values less than (3),
5 partition p3 values less than (4),
6 partition p4 values less than (5),
7 partition p5 values less than (6) )
8 as
9 select a.*, mod(rownum,5)+1 x
10 from all_objects a
11 where rownum <=10;
Table created.
SQL>
SQL> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.
SQL>
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 172 | 5 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 2 | 172 | 5 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | T | 2 | 172 | 5 (0)| 00:00:01 | 2 | 2 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("X"=2)
So far so good. The table is tiny (only 10 rows, and 2 rows per partition) and hence the optimizer has got this estimate spot on. Now we will
- inflate one of the partitions with another ~100,000 rows.
- turn off the publishing of statistics
- gather a set of pending statistics with the new data
SQL>
SQL> set autotrace off
SQL>
SQL> insert into t
2 select a.*, 2 x
3 from all_objects a;
96983 rows created.
SQL>
SQL> select dbms_stats.get_prefs('PUBLISH',user,'T') from dual;
DBMS_STATS.GET_PREFS('PUBLISH',USER,'T')
----------------------------------------------------------------------------------
TRUE
1 row selected.
SQL>
SQL> exec dbms_stats.set_table_prefs(user,'T','PUBLISH','FALSE');
PL/SQL procedure successfully completed.
SQL>
SQL> show parameter optimizer_use_pend
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
optimizer_use_pending_statistics boolean FALSE
SQL> select dbms_stats.get_prefs('PUBLISH',user,'T') from dual;
DBMS_STATS.GET_PREFS('PUBLISH',USER,'T')
-------------------------------------------------------------------------------
FALSE
1 row selected.
SQL>
SQL> select num_rows,blocks,last_analyzed
2 from user_tab_statistics
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS LAST_ANAL
---------- ---------- ---------
2 19 12-OCT-16
1 row selected.
SQL> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.
SQL>
SQL> select num_rows,blocks,last_analyzed
2 from user_tab_statistics
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS LAST_ANAL
---------- ---------- ---------
2 19 12-OCT-16
1 row selected.
SQL>
SQL> select num_rows,blocks
2 from user_tab_pending_stats
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS
---------- ----------
96985 2032
1 row selected.
At this point, optimization should be unchanged, because we have only collected pending statistics – and we can see that this is the case
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 172 | 5 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 2 | 172 | 5 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | T | 2 | 172 | 5 (0)| 00:00:01 | 2 | 2 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("X"=2)
Once again, so far so good. Now we will change our session so that it picks up the pending statistics
SQL> alter session set optimizer_use_pending_statistics=true;
Session altered.
SQL>
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 172 | 7 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 2 | 172 | 7 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | T | 2 | 172 | 7 (0)| 00:00:01 | 2 | 2 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("X"=2)
And….. ker-splat . We still didn’t pick up the pending statistics. This is the known issue, and it occurs when accessing pending statistics for a single partition. If we go ahead and publish the statistics, then we’ll see the correct numbers come into the optimizer consideration.
SQL> exec dbms_stats.PUBLISH_PENDING_STATS(user,'T');
PL/SQL procedure successfully completed.
SQL>
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 96976 | 10M| 556 (1)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 96976 | 10M| 556 (1)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | T | 96976 | 10M| 556 (1)| 00:00:01 | 2 | 2 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("X"=2)
SQL>
So this is just something to be aware of if you are using pending statistics and partitioned tables.
And there is an easy way to fix this – just move to our Exadata Cloud Express, where the issue is no longer present !
SQL*Plus: Release 12.1.0.2.0 Production on Wed Oct 12 14:01:26 2016
Copyright (c) 1982, 2014, Oracle. All rights reserved.
Last Successful login time: Tue Oct 11 2016 10:58:30 +08:00
Connected to:
Oracle Database 12c Enterprise Edition Release 12.2.0.0.3 - 64bit Production
SQL> create table t
2 partition by range(x)(
3 partition p1 values less than (2),
4 partition p2 values less than (3),
5 partition p3 values less than (4),
6 partition p4 values less than (5),
7 partition p5 values less than (6) )
8 as
9 select a.*, mod(rownum,5)+1 x
10 from all_objects a
11 where rownum <=10;
Table created.
SQL>
SQL> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.
SQL>
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 214 | 2 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE | | 2 | 214 | 2 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS STORAGE FULL| T | 2 | 214 | 2 (0)| 00:00:01 | 2 | 2 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - storage("X"=2)
filter("X"=2)
SQL>
SQL> set autotrace off
SQL>
SQL> insert into t
2 select a.*, 2 x
3 from all_objects a;
62286 rows created.
SQL>
SQL> select dbms_stats.get_prefs('PUBLISH',user,'T') from dual;
DBMS_STATS.GET_PREFS('PUBLISH',USER,'T')
------------------------------------------------------------------------
TRUE
SQL>
SQL> exec dbms_stats.set_table_prefs(user,'T','PUBLISH','FALSE');
PL/SQL procedure successfully completed.
SQL>
SQL> show parameter optimizer_use_pend
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
optimizer_use_pending_statistics boolean FALSE
SQL>
SQL> select dbms_stats.get_prefs('PUBLISH',user,'T') from dual;
DBMS_STATS.GET_PREFS('PUBLISH',USER,'T')
--------------------------------------------------------------------------------
FALSE
SQL>
SQL> select num_rows,blocks,last_analyzed
2 from user_tab_statistics
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS LAST_ANAL
---------- ---------- ---------
2 1 12-OCT-16
SQL>
SQL> --exec dbms_stats.gather_table_stats(user,'T',partname=>'P2');
SQL> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.
SQL>
SQL> select num_rows,blocks,last_analyzed
2 from user_tab_statistics
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS LAST_ANAL
---------- ---------- ---------
2 1 12-OCT-16
SQL>
SQL> select num_rows,blocks
2 from user_tab_pending_stats
3 where table_name ='T'
4 and partition_name ='P2';
NUM_ROWS BLOCKS
---------- ----------
62288 1273
SQL>
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 214 | 2 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE | | 2 | 214 | 2 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS STORAGE FULL| T | 2 | 214 | 2 (0)| 00:00:01 | 2 | 2 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - storage("X"=2)
filter("X"=2)
SQL>
SQL> alter session set optimizer_use_pending_statistics=true;
Session altered.
SQL>
SQL> set autotrace traceonly explain
SQL> select * from t where x = 2;
Execution Plan
----------------------------------------------------------
Plan hash value: 2931986080
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 62282 | 8271K| 348 (1)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE | | 62282 | 8271K| 348 (1)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS STORAGE FULL| T | 62282 | 8271K| 348 (1)| 00:00:01 | 2 | 2 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - storage("X"=2)
filter("X"=2)
See ? The cloud fixes everything