This is a small article to demonstrate why correct statistics are important and how they affect execution statistics of same plan.
In the past we learned that changing table statistics or index statistics (or rebuilding index) can causes plan for a SQL to change. Because when statistics changes, optimizer will try to generate new plan based on changed statistics.
With 11g, oracle provided baseline to ensure stability in SQL plans. So if you have single baseline enabled for a SQL, you essentially have single plan for that SQL and that plan will not change unless auto tuning job evolve another plan or you manually evolves another plan.
Does it mean that object statistics has no role to play if your plan is fixed ?
Lets have a quick demo. In my below exercise, I will not be changing the plan (as I am using baseline). But we will see that execution statistics such as buffer_gets and disk reads changes as you change object statistics.
I faced this issue on one of our production database where stats were completely inaccurate and when we gathered stats on tables and indexes, execution stats for SQLs changed with same plan.
Setup
I am performing these tests on 11.2.0.4.
Lets create 2 tables T1 and T2. I will use one query on single table to go for FTS and other query joining T1 and T2 and use index.
SQL>create table T1 as select * from dba_tables; Table created. SQL>insert into T1 select * from T1; 2804 rows created. SQL>insert into T1 select * from T1; 5608 rows created. ... ... SQL>commit; Commit complete. SQL>--create index on this table on TABLE_NAME column and gather stats SQL>create index I_T1_TABLE_NAME on T1(TABLE_NAME); Index created. SQL>exec dbms_stats.gather_table_stats(null,'T1'); PL/SQL procedure successfully completed.
Create 2nd table from some other view, lets say dba_tab_statistics
DEO>create table T2 as select * from dba_tab_statistics; Table created. DEO>insert into T2 select * from T2; 16289 rows created. ... ... DEO>commit; Commit complete. DEO>--create index on TABLE_NAME column in T2 table DEO>create index I_T2_TABLE_NAME on T2(TABLE_NAME); Index created. DEO>exec dbms_stats.gather_table_stats(null,'T2'); PL/SQL procedure successfully completed.
Following are the table and index level stats values for T1 and T2 and corresponding indexes
DEO>select table_name, num_rows, blocks, avg_row_len from dba_tables where table_name in ('T1','T2'); TABLE_NAME NUM_ROWS BLOCKS AVG_ROW_LEN ------------------------------ ---------- ---------- ----------- T1 34783 3214 247 T2 298096 4351 99 DEO>select INDEX_NAME, LEAF_BLOCKS, DISTINCT_KEYS, CLUSTERING_FACTOR, NUM_ROWS from dba_indexes where table_name in ('T1','T2'); INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR NUM_ROWS ------------------------------ ----------- ------------- ----------------- ---------- I_T1_TABLE_NAME 360 1414 34675 34783 I_T2_TABLE_NAME 1813 2364 74610 298096
So when object statistics are accurate and current, I see following execution statistics for following 2 queries
Query 1:
select a.table_name, sum(b.num_rows) from T1 a, T2 b where a.table_name= b.table_name and a.owner = b.owner and a.table_name = :b1 group by a.table_name;
DEO>var b1 varchar2(40); DEO>exec :b1 := 'ABC'; PL/SQL procedure successfully completed. DEO>select a.table_name, sum(b.num_rows) from T1 a, T2 b where a.table_name= b.table_name and a.owner = b.owner and a.table_name = :b1 group by a.table_name; TABLE_NAME SUM(B.NUM_ROWS) ------------------------------ --------------- ABC 240678640 Execution Plan ---------------------------------------------------------- Plan hash value: 1483037242 -------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 61 | 4 (50)| 00:00:01 | | 1 | SORT GROUP BY NOSORT | | 1 | 61 | 4 (50)| 00:00:01 | | 2 | MERGE JOIN | | 47 | 2867 | 4 (50)| 00:00:01 | | 3 | SORT JOIN | | 11 | 396 | 2 (50)| 00:00:01 | | 4 | VIEW | VW_GBC_5 | 11 | 396 | 2 (50)| 00:00:01 | | 5 | HASH GROUP BY | | 11 | 297 | 2 (50)| 00:00:01 | | 6 | TABLE ACCESS BY INDEX ROWID | T2 | 126 | 3402 | 1 (0)| 00:00:01 | |* 7 | INDEX RANGE SCAN | I_T2_TABLE_NAME | 126 | | 1 (0)| 00:00:01 | |* 8 | SORT JOIN | | 25 | 625 | 2 (50)| 00:00:01 | | 9 | TABLE ACCESS BY INDEX ROWID | T1 | 25 | 625 | 1 (0)| 00:00:01 | |* 10 | INDEX RANGE SCAN | I_T1_TABLE_NAME | 25 | | 1 (0)| 00:00:01 | -------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 7 - access("B"."TABLE_NAME"=:B1) 8 - access("A"."TABLE_NAME"="ITEM_2" AND "A"."OWNER"="ITEM_1") filter("A"."OWNER"="ITEM_1" AND "A"."TABLE_NAME"="ITEM_2") 10 - access("A"."TABLE_NAME"=:B1) Note ----- - SQL plan baseline "SQL_PLAN_gt8npsxnncgm2abc84fa9" used for this statement Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 157 consistent gets 3 physical reads 0 redo size 598 bytes sent via SQL*Net to client 453 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 1 rows processed
Above query has done 157 consistent gets and 3 physical reads.
Query 2:
select count(1) from T2 where owner = :b2;
DEO>var b2 varchar2(40); DEO>exec :b2 := 'SYS'; PL/SQL procedure successfully completed. DEO>select count(1) from T2 where owner = :b2; COUNT(1) ---------- 251088 Execution Plan ---------------------------------------------------------- Plan hash value: 3321871023 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 5 | 625 (2)| 00:00:03 | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | TABLE ACCESS FULL| T2 | 19873 | 99365 | 625 (2)| 00:00:03 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("OWNER"=:B2) Note ----- - SQL plan baseline "SQL_PLAN_9wq67xmrafzda1c6cf506" used for this statement Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 4350 consistent gets 0 physical reads 0 redo size 517 bytes sent via SQL*Net to client 453 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Above query has done 4350 consistent gets and no physical reads.
Lets fake statistics to random high value and see the affect. Note that both queries above are using baseline, so plan will not change with further executions even after changing object stats
I am going to set table stats and index stats as follows
DEO>exec dbms_stats.SET_TABLE_STATS(null,'T1',NUMROWS=>3221020,NUMBLKS=>202590, AVGRLEN=>150) PL/SQL procedure successfully completed. DEO>exec dbms_stats.SET_INDEX_STATS(null,'I_T1_TABLE_NAME',NUMROWS=>3153430,NUMLBLKS=>124232,NUMDIST=>6,AVGLBLK=>2990,AVGDBLK=>19002,CLSTFCT=>76010) PL/SQL procedure successfully completed. DEO>exec dbms_stats.SET_TABLE_STATS(null,'T2',NUMROWS=>140000000,NUMBLKS=>13540202, AVGRLEN=>120) PL/SQL procedure successfully completed. DEO>exec dbms_stats.SET_INDEX_STATS(null,'I_T2_TABLE_NAME',NUMROWS=>13345304,NUMLBLKS=>1242022,NUMDIST=>12,AVGLBLK=>324,AVGDBLK=>1342,CLSTFCT=>260123) PL/SQL procedure successfully completed. DEO>select table_name, num_rows, blocks, avg_row_len from dba_tables where table_name in ('T1','T2'); TABLE_NAME NUM_ROWS BLOCKS AVG_ROW_LEN ------------------------------ ---------- ---------- ----------- T1 3221020 202590 150 T2 140000000 13540202 120 DEO>select INDEX_NAME, LEAF_BLOCKS, DISTINCT_KEYS, CLUSTERING_FACTOR, NUM_ROWS from dba_indexes where table_name in ('T1','T2'); INDEX_NAME LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR NUM_ROWS ------------------------------ ----------- ------------- ----------------- ---------- I_T1_TABLE_NAME 124232 6 76010 3153430 I_T2_TABLE_NAME 1242022 12 260123 13345304
I will purge those queries from shared_pool and run those same queries again
DEO>!cat purgesql.sql accept sql_id prompt 'Enter SQL_ID:- ' DECLARE name varchar2(50); BEGIN select distinct address||','||hash_value into name from v$sqlarea where sql_id like '&sql_id'; sys.dbms_shared_pool.purge(name,'C',65); END; / DEO>@purgesql Enter SQL_ID:- 46j56265bmw7u PL/SQL procedure successfully completed. DEO>@purgesql Enter SQL_ID:- gkbtfpmvxw4hn PL/SQL procedure successfully completed.
Lets run the queries again after changing the stats
Query 1:
select a.table_name, sum(b.num_rows) from T1 a, T2 b where a.table_name= b.table_name and a.owner = b.owner and a.table_name = :b1 group by a.table_name;
DEO>set autotrace on DEO>select a.table_name, sum(b.num_rows) from T1 a, T2 b where a.table_name= b.table_name and a.owner = b.owner and a.table_name = :b1 group by a.table_name; TABLE_NAME SUM(B.NUM_ROWS) ------------------------------ --------------- ABC 240678640 Execution Plan ---------------------------------------------------------- Plan hash value: 1483037242 ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 61 | 74 (9)| 00:00:01 | | 1 | SORT GROUP BY NOSORT | | 1 | 61 | 74 (9)| 00:00:01 | | 2 | MERGE JOIN | | 4464 | 265K| 74 (9)| 00:00:01 | | 3 | SORT JOIN | | 11 | 396 | 71 (6)| 00:00:01 | | 4 | VIEW | VW_GBC_5 | 11 | 396 | 71 (6)| 00:00:01 | | 5 | HASH GROUP BY | | 11 | 297 | 71 (6)| 00:00:01 | | 6 | TABLE ACCESS BY INDEX ROWID | T2 | 59222 | 1561K| 67 (0)| 00:00:01 | |* 7 | INDEX RANGE SCAN | I_T2_TABLE_NAME | 59222 | | 55 (0)| 00:00:01 | |* 8 | SORT JOIN | | 2278 | 56950 | 3 (67)| 00:00:01 | | 9 | TABLE ACCESS BY INDEX ROWID | T1 | 2278 | 56950 | 1 (0)| 00:00:01 | |* 10 | INDEX RANGE SCAN | I_T1_TABLE_NAME | 2278 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 7 - access("B"."TABLE_NAME"=:B1) 8 - access("A"."TABLE_NAME"="ITEM_2" AND "A"."OWNER"="ITEM_1") filter("A"."OWNER"="ITEM_1" AND "A"."TABLE_NAME"="ITEM_2") 10 - access("A"."TABLE_NAME"=:B1) Note ----- - SQL plan baseline "SQL_PLAN_gt8npsxnncgm2abc84fa9" used for this statement Statistics ---------------------------------------------------------- 26 recursive calls 62 db block gets 231 consistent gets 4 physical reads 14528 redo size 598 bytes sent via SQL*Net to client 453 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 1 rows processed
Note that plan didn’t change, but we see that consistent gets (buffer_gets/exec) has increased from previous value of 157 to 231.
Physical reads are more or less same (just increase of 1).
Lets check 2nd query doing FTS
Query 2:
select count(1) from T2 where owner = :b2;
DEO>set autotrace on DEO>select count(1) from T2 where owner = :b2; COUNT(1) ---------- 9600 Execution Plan ---------------------------------------------------------- Plan hash value: 3321871023 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 5 | 1926K (1)| 02:10:54 | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | TABLE ACCESS FULL| T2 | 9333K| 44M| 1926K (1)| 02:10:54 | --------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("OWNER"=:B2) Note ----- - SQL plan baseline "SQL_PLAN_9wq67xmrafzda1c6cf506" used for this statement Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 8195 consistent gets 4342 physical reads 0 redo size 515 bytes sent via SQL*Net to client 453 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Huge increase in consistent gets compared to previous run. Previous run showed consistent gets as 4350 when stats were accurate. With increased stats, consistent gets became 8195
Also, there was no disk reads previously may be because all blocks were in buffer. But with changed object level stats, we are seeing 4342 disk reads.
So always make sure you have latest accurate statistics available for your tables and indexes in your database. You may have right plans, but having bad object stats can cause Oracle to work more.
Hope this helps !!
Filed under: Oracle Database 11g, Performance Tuninig Tagged: buffer_gets, execution statistics, index stats, object statistics, statistics, table stats Image may be NSFW.
Clik here to view.
Clik here to view.
