Database & Technology 2 _ Richard Foote _ 10 things you probably dont know about Oracle's...
-
Upload
insync2011 -
Category
Technology
-
view
902 -
download
0
Transcript of Database & Technology 2 _ Richard Foote _ 10 things you probably dont know about Oracle's...
The most comprehensive Oracle applications & technology content under one roof The most comprehensive Oracle applications & technology content under one roof
10 Things You Might Not Know About Oracle Indexes
Richard Foote
The most comprehensive Oracle applications & technology content under one roof
Richard Foote • Working in IT for 25 years (scary stuff) • Working with Oracle for 15 years (almost as scary) • Previously employed by Oracle CorporaFon for 5 ½ years (scary as hell) • Currently employed by the Australian Federal Government as a Senior
DBA • Responsible for many large scale, mission criFcal, “life-‐dependant”
classified Oracle systems • Based in sunny Canberra, Australia • Oracle OakTable member since 2002 and Oracle ACE Director since 2008 • Interests includes all sports and music (cool stuff like David Bowie, Pink
Floyd, Radiohead and Muse) • [email protected] • Richard Foote’s Oracle Blog: hBp://richardfoote.wordpress.com/
The most comprehensive Oracle applications & technology content under one roof
1. Delete More Rows To Reduce Deleted Space Within An Index
• Very common advice to rebuild index if percentage of deleted space > 20%
• For example -‐ Metalink Note: 122008.1 recommended this for many years
• Validate index structure (Ignoring that it’s expensive and locks the table)
• Claimed rebuild will remove index “deadwood” and make index more efficient
• However, if only we just deleted even more rows ...
The most comprehensive Oracle applications & technology content under one roof
1: If deleted space >20%, delete some more!! SQL> create table radiohead (id number, name varchar2(20)); Table created. SQL> insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 100000; 100000 rows created. SQL> commit; Commit complete. SQL> create index radiohead_i on radiohead(id) pckree 0; Index created. SQL> analyze index radiohead_i validate structure; Index analyzed. SQL> select lf_rows, del_lf_rows, round((del_lf_rows/lf_rows)*100) PCT_DEL, pct_used from index_stats; LF_ROWS DEL_LF_ROWS PCT_DEL PCT_USED ---------- ----------- ---------- ---------- 100000 0 0 100
The most comprehensive Oracle applications & technology content under one roof
1: If deleted space >20%, delete some more!! SQL> delete radiohead where mod(id,10) IN (2,4,6); 30000 rows deleted. SQL> commit; Commit complete. SQL> analyze index radiohead_i validate structure; Index analyzed. SQL> select lf_rows, del_lf_rows, (del_lf_rows/lf_rows)*100 PCT_DEL, pct_used from index_stats; LF_ROWS DEL_LF_ROWS PCT_DEL PCT_USED ---------- ----------- ---------- ---------- 100000 30000 30 100
So based on common rebuild criteria, this index really should be rebuilt ...
The most comprehensive Oracle applications & technology content under one roof
1: If deleted space >20%, delete some more!!
SQL> delete radiohead where mod(id,10) = 8; 10000 rows deleted. SQL> commit; Commit complete. SQL> analyze index radiohead_i validate structure; Index analyzed. SQL> select lf_rows, del_lf_rows, round((del_lf_rows/lf_rows)*100) PCT_DEL, pct_used from index_stats; LF_ROWS DEL_LF_ROWS PCT_DEL PCT_USED ---------- ----------- ---------- ---------- 70000 10000 14 70
Well, let’s just delete some more rows ...
The most comprehensive Oracle applications & technology content under one roof
1: If deleted space >20%, delete some more!!
• What many don’t realise is that in most scenarios, deleted space is the same as free space
• It will generally eventually get reused • To clean out deleted space, it just takes the leaf block to be modified by a subsequent transacFon
• The criteria actually rebuilds indexes that don’t need rebuilding, and
• The criteria misses out on those indexes that might actually need rebuilding
The most comprehensive Oracle applications & technology content under one roof
2.Bitmap Index High Cardinality Columns • Very common advice to only use Bitmap Index with “Low” Cardinality Columns
• Generally columns with less than 20 disFnct values • Examples: Eye/Hair Colour, Sex, States of Australia... • A column with say 10,000 disFnct values would be far too many and result in huge, inefficient index
• A column on say person first name or surname would be totally unsuitable for a Bitmap Index
• But would it really ...
The most comprehensive Oracle applications & technology content under one roof
2.Bitmap Index High Cardinality Columns SQL> CREATE TABLE big_dwh_table (id NUMBER, album_id NUMBER, arFst_id NUMBER, country_id NUMBER, format_id NUMBER, release_date DATE, total_sales NUMBER); Table created. SQL> CREATE SEQUENCE dwh_seq; Sequence created. SQL> create or replace procedure pop_big_dwh_table as 2 v_id number; 3 v_arFst_id number; 4 begin 5 for v_album_id in 1..10000 loop 6 v_arFst_id:= ceil(dbms_random.value(0,100)); 7 for v_country_id in 1..100 loop 8 select dwh_seq.nextval into v_id from dual; 9 insert into big_dwh_table values (v_id, v_album_id, v_arFst_id, vcountry_id, ceil(dbms_random.value(0,4)), trunc(sysdate-‐mod(v_id,ceil(dbms_random.value(0,1000)))), ceil(dbms_random.value(0,500000))); 10 end loop; 11 end loop; 12 commit; 13 end; 14 / Procedure created. SQL> exec pop_big_dwh_table PL/SQL procedure successfully completed.
The most comprehensive Oracle applications & technology content under one roof
2.Bitmap Index High Cardinality Columns
SQL> CREATE INDEX big_dwh_album_id_i ON big_dwh_table(album_id) COMPUTE STATISTICS; Index created. SQL> SELECT i.index_type, i.disFnct_keys, t.num_rows "T ROWS", t.blocks "T BLOCKS", i.num_rows "I ROWS", i.clustering_factor CF, i.leaf_blocks FROM user_indexes i, user_tables t WHERE i.table_name=t.table_name and i.index_name = 'BIG_DWH_ALBUM_ID_I'; INDEX_TYPE DISTINCT_KEYS T ROWS T BLOCKS I ROWS CF LEAF_BLOCKS ---------- ------------- ---------- ---------- ---------- ---------- ----------- NORMAL 10000 1000000 4948 1000000 4951 2090
OK, let’s start with a B-‐Tree Index
Note the album_id column has a relaFvely “high” cardinality at 10,000 disFnct values. Also note the B-‐Tree Index is extremely well clustered (CF matches blocks in table) and has 2090 leaf blocks ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns SQL> SELECT * FROM big_dwh_table WHERE album_id = 42; 100 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)| Time | -------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 3000 | 103 (0)|00:00:02| | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE_2 | 100 | 3000 | 103 (0)|00:00:02| |* 2 | INDEX RANGE SCAN | BIG_DWH_2_ALBUM_ID_I | 100 | | 3 (0)|00:00:01| ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 19 consistent gets 100 rows processed
Note: As expected, the index is used to retrieve the relaFvely few 100 rows (0.01% of data). Just 19 consistent reads required ...
The most comprehensive Oracle applications & technology content under one roof
2.Bitmap Index High Cardinality Columns
SQL> DROP INDEX big_dwh_album_id_i; Index dropped. SQL> CREATE BITMAP INDEX big_dwh_album_id_i ON big_dwh_table(album_id); Index created. SQL> SELECT index_name, index_type, disFnct_keys, num_rows, leaf_blocks, blocks FROM user_indexes i, user_segments s WHERE i.index_name = s.segment_name and i.index_name = 'BIG_DWH_ALBUM_ID_I'; INDEX_NAME INDEX_TYPE DISTINCT_KEYS NUM_ROWS LEAF_BLOCKS BLOCKS ------------------ ---------- ------------- -------- ----------- ------ BIG_DWH_ALBUM_ID_I BITMAP 10000 10000 56 128
Let’s create same index as a Bitmap Index. However, with 10,000 disFnct values, unlikely to be efficient, right ?
Wrong. The number of index rows is just 10000, one for each disFnct value and leaf blocks has dropped from 2090 to just 56 !!
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns SQL> SELECT * FROM big_dwh_table WHERE album_id = 42; 100 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 3000 | 22 (0)|00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID | BIG_DWH_TABLE | 100 | 3000 | 22 (0)|00:00:01| | 2 | BITMAP CONVERSION TO ROWIDS| | | | | | |* 3 | BITMAP INDEX SINGLE VALUE | BIG_DWH_ALBUM_ID_I | | | | | ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 11 consistent gets 100 rows processed
Note: Not only is the index used, but the number of consistent reads has dropped from 19 to just 11 !!
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns The Clustering Factor of an index has a big impact on the overall size of a Bitmap Index This is due to having more conFnuous zeros in the bitmap string that can be more efficiently compressed
SQL> SELECT i.index_name, i.index_type, t.num_rows "TABLE ROWS", t.blocks, i.num_rows "INDEX ROWS", i.clustering_factor CF, i.leaf_blocks FROM user_indexes i, user_tables t WHERE i.table_name=t.table_name and i.index_name = 'BIG_DWH_ALBUM_ID_I'; INDEX_NAME INDEX_TYPE TABLE ROWS BLOCKS INDEX ROWS CF LEAF_BLOCKS ------------------ ---------- ---------- ------- ---------- ------ ----------- BIG_DWH_ALBUM_ID_I BITMAP 1000000 4948 10000 10000 56
Note the CF as recorded for Bitmap Indexes is a liyle meaningless as a specific index entry covers many Rowids but the CF calculaFon is sFll based on the block within the rowid changing from one index entry to the next. Therefore, CF of an index is commonly recorded as being the number of index row entries
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
SQL> CREATE TABLE big_dwh_table_2 AS SELECT * FROM big_dwh_table ORDER BY total_sales; Table created. SQL> CREATE BITMAP INDEX big_dwh_2_album_id_i ON big_dwh_table_2(album_id); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=> 'BIG_DWH_TABLE_2', esFmate_percent=> null, cascade=> true, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
Create a copy of the table but this Fme clustered by the TOTAL_SALES column Then create a Bitmap Index on the now poorly clustered ALBUM_ID column
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
SQL> SELECT i.index_name, i.index_type, t.num_rows "TABLE ROWS", t.blocks, i.num_rows "INDEX ROWS", i.clustering_factor CF, i.leaf_blocks FROM user_indexes i, user_tables t WHERE i.table_name=t.table_name and i.index_name = 'BIG_DWH_2_ALBUM_ID_I'; INDEX_NAME INDEX_TYPE TABLE ROWS BLOCKS INDEX ROWS CF LEAF_BLOCKS -------------------- ---------- ---------- ------- ---------- ------ ----------- BIG_DWH_2_ALBUM_ID_I BITMAP 1000000 4973 10000 10000 455
Note: Leaf Blocks has jumped up significantly from 56 blocks to 455 blocks
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns SQL> SELECT * FROM big_dwh_table_2 WHERE album_id = 42; 100 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)|Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100| 3000 | 22 (0)|00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID | BIG_DWH_TABLE_2 | 100| 3000 | 22 (0)|00:00:01| | 2 | BITMAP CONVERSION TO ROWIDS| | | | | | |* 3 | BITMAP INDEX SINGLE VALUE | BIG_DWH_2_ALBUM_ID_I | | | | | ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 101 consistent gets 100 rows processed
Note: The number of consistent reads has now jumped up from 11 to 101 ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
SQL> DROP INDEX big_dwh_2_album_id_i; Index dropped. SQL> CREATE INDEX big_dwh_2_album_id_i ON big_dwh_table_2(album_id); Index created. SQL> SELECT i.index_name, i.index_type, t.num_rows "TABLE ROWS", t.blocks, i.num_rows "INDEX ROWS", i.clustering_factor CF, i.leaf_blocks FROM user_indexes i, user_tables t WHERE i.table_name=t.table_name and i.index_name = 'BIG_DWH_2_ALBUM_ID_I'; INDEX_NAME INDEX_TYPE TABLE ROWS BLOCKS INDEX ROWS CF LEAF_BLOCKS -------------------- ---------- ---------- ------- ---------- ------- ----------- BIG_DWH_2_ALBUM_ID_I NORMAL 1000000 4973 1000000 990003 2090
Of course, this will also impact Clustering Factor of a B-‐Tree Index ...
Although the index has same no. of leaf blocks, the Clustering Factor has jumped to a massive 990003 ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns SQL> SELECT * FROM big_dwh_table_2 WHERE album_id = 42; 100 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 3000 | 103 (0)|00:00:02| | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE_2 | 100 | 3000 | 103 (0)|00:00:02| |* 2 | INDEX RANGE SCAN | BIG_DWH_2_ALBUM_ID_I | 100 | | 3 (0)|00:00:01| ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 109 consistent gets 100 rows processed
Consistent reads has jumped from 19 to 109 which is sFll worse than the 101 consistent reads of the Bitmap Index ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
NAME INDEX_TYPE NUM_ROWS DISTINCT_KEYS LEAF_BLOCKS ---------- ------------ ---------- ------------- ----------- FIRST NAME NORMAL 2378672 147446 5842 LAST NAME NORMAL 2405599 361177 6202
SQL> select * from persons where given_name1 = 'DAVID' or last_name = 'BOWIE'; 39240 rows selected. Execution Plan ---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 174 | 36888 | 164 (0)| 00:00:01 | | 1 | CONCATENATION | | | | | | | 2 | TABLE ACCESS BY INDEX ROWID| PERSONS | 47 | 9964 | 44 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | PERS_LAST_NAME_I | 47 | | 3 (0)| 00:00:01 | |* 4 | TABLE ACCESS BY INDEX ROWID| PERSONS | 127 | 26924 | 120 (0)| 00:00:01 | |* 5 | INDEX RANGE SCAN | PERS_FIRST_NAME1_I | 127 | | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------
Statistics ---------------------------------------------------------- 28600 consistent gets
Table containing person names, using freshly rebuilt B-‐Tree Indexes ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
NAME INDEX_TYPE NUM_ROWS DISTINCT_KEYS LEAF_BLOCKS ---------- ------------ ---------- ------------- ----------- FIRST NAME BITMAP 148208 147463 1608 (-4234) LAST NAME BITMAP 361421 361183 2543 (-3659)
SQL> select * from persons where given_name1 = 'DAVID' or last_name = 'BOWIE'; 39240 rows selected. Execution Plan --------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 176 | 37312 | 43 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID | PERSONS | 176 | 37312 | 43 (0)| 00:00:01 | | 2 | BITMAP CONVERSION TO ROWIDS| | | | | | | 3 | BITMAP OR | | | | | | |* 4 | BITMAP INDEX SINGLE VALUE| PERS_FIRST_NAME1_I | | | | |
|* 5 | BITMAP INDEX SINGLE VALUE| PERS_LAST_NAME_I | | | | | --------------------------------------------------------------------------------------------------- Statistics
---------------------------------------------------------- 28483 consistent gets
Bitmap Indexes are actually substanFally smaller and more efficient ...
The most comprehensive Oracle applications & technology content under one roof
2. Bitmap Index High Cardinality Columns
• If you only use Bitmap Indexes for low cardinality columns, you’re not taking full advantage of them
• Suitable for all columns that are not approaching uniqueness
• BUT, only for Data Warehouse environments due to locking implicaFons of concurrent transacFons within same table
The most comprehensive Oracle applications & technology content under one roof
3. B-‐Tree Index Low Cardinality Columns
• Generally B-‐Tree Indexes more suitable for columns with many disFnct values
• Increases selecFvity and makes indexes a more efficient opFon to the CBO
• However, there are always excepFons • B-‐Tree Index might be the perfect candidate for a column with as low as 1 disFnct value ...
The most comprehensive Oracle applications & technology content under one roof
3. B-‐Tree Index Low Cardinality Columns SQL> create table bowie (id number, code number, name varchar2(50)); Table created. SQL> create index bowie_code_i on bowie(code); Index created. SQL> insert into bowie select rownum, null, 'Ziggy Stardust and the Spiders From Mars' from dual connect by level <= 1000000; 1000000 rows created. SQL> update bowie set code = 42 where mod(id,10000) = 0; 100 rows updated. SQL> commit; Commit complete. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>'BOWIE', cascade=> true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
CODE column only has 1 disFnct value although most rows are NULL ...
The most comprehensive Oracle applications & technology content under one roof
3. B-‐Tree Index Low Cardinality Columns SQL> select blevel, leaf_blocks, disFnct_keys from dba_indexes where index_name='BOWIE_CODE_I'; BLEVEL LEAF_BLOCKS DISTINCT_KEYS ---------- ----------- ------------- 0 1 1
SQL> select column_name, num_disFnct, num_nulls from dba_tab_columns where table_name = 'BOWIE' and column_name = 'CODE'; COLUMN_NAME NUM_DISTINCT NUM_NULLS ------------ ------------ ---------- CODE 1 999900
As NULLs are not stored in B-‐Tree Indexes by default, index is Fny CBO knows there’s only 1 disFnct value but it also knows most rows are only NULLs
The most comprehensive Oracle applications & technology content under one roof
3. B-‐Tree Index Low Cardinality Columns SQL> select * from bowie where code = 42; 100 rows selected. Execution Plan -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 4700 | 101 (0)| 00:00:02 | | 1 | TABLE ACCESS BY INDEX ROWID| BOWIE | 100 | 4700 | 101 (0)| 00:00:02 | |* 2 | INDEX RANGE SCAN | BOWIE_CODE_I | 100 | | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------------
CBO will happily use index on CODE column with 1 disFnct value No histograms necessary as exisFng staFsFcs sufficient for CBO to know index is the most efficient access path ...
The most comprehensive Oracle applications & technology content under one roof
4. Index Just Which Column Values You Want
• By default, all values within a column are indexed • There are many scenarios when you may not want to index all values within a column
• Some column values maybe too numerous to be of pracFcal use within the index
• Some column values may unnecessarily inflate size and efficiency of index
• So, just don't index them ...
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> CREATE TABLE index_some_stuff (id number, status varchar2(20), descripFon varchar2(50)); Table created. SQL> INSERT INTO index_some_stuff SELECT rownum, 'PROCESSED', 'NOT REALLY INTERESTED WITH THIS ROW' FROM DUAL CONNECT BY LEVEL <= 1000000; 1000000 rows created. SQL> UPDATE index_some_stuff SET status = 'BOWIE', descripFon = 'ROW OF INTEREST' where mod(id,10000)=42; 100 rows updated. SQL> commit; Commit complete. SQL> SELECT status, count(*) FROM index_some_stuff GROUP BY status; STATUS COUNT(*) -------------------- ---------- PROCESSED 999900 BOWIE 100
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> CREATE INDEX index_some_stuff_i_1 ON index_some_stuff(status); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=> ’INDEX_SOME_STUFF', method_opt=> 'FOR COLUMNS STATUS SIZE 5'); PL/SQL procedure successfully completed. SQL> SELECT * FROM index_some_stuff WHERE status = 'BOWIE'; 100 rows selected. Execution Plan ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 5200 | 4 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| INDEX_SOME_STUFF | 100 | 5200 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | INDEX_SOME_STUFF_I_1 | 100 | | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 104 consistent gets 100 rows processed
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values But why bother indexing all the common values when they’ll never be referenced ? SQL> CREATE INDEX index_some_stuff_i_2 ON index_some_stuff(DECODE(status, 'BOWIE', 'BOWIE', NULL)) COMPUTE STATISTICS; Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=> ‘INDEX_SOME_STUFF', method_opt=>'FOR ALL HIDDEN COLUMNS SIZE 1', cascade=> true); PL/SQL procedure successfully completed. SQL> SELECT index_name, blevel, leaf_blocks, disFnct_keys, num_rows FROM dba_indexes WHERE owner='BOWIE' AND table_name='INDEX_SOME_STUFF'; INDEX_NAME BLEVEL LEAF_BLOCKS DISTINCT_KEYS NUM_ROWS ------------------------------ ---------- ----------- ------------- ---------- INDEX_SOME_STUFF_I_2 0 1 1 100 INDEX_SOME_STUFF_I_1 2 2924 2 1000000
The index is only a fracFon the size of the previous index as it only contains the one index entry of interest
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> SELECT * FROM bowie.index_some_stuff WHERE(DECODE(status, 'BOWIE', 'BOWIE', null)) = 'BOWIE'; 100 row selected. ----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100 | 5300 | 101 (0)| 00:00:02 | | 1 | TABLE ACCESS BY INDEX ROWID| INDEX_SOME_STUFF | 100 | 5300 | 101 (0)| 00:00:02 | |* 2 | INDEX RANGE SCAN | INDEX_SOME_STUFF_I_2 | 100 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 102 consistent gets 100 rows processed
Note: consistent gets have now reduced down from 104 to 102
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values
SQL> create table bowie_stuff (id number, processed varchar2(10)); Table created. SQL> insert into bowie_stuff select rownum, 'YES' from dual connect by level <= 1000000; 1000000 rows created. SQL> commit; Commit complete. SQL> update bowie_stuff set processed = ‘NO’ where id in (999990, 999992, 999994, 999996, 999998); 5 rows updated. SQL> commit; Commit complete. SQL> create index bowie_stuff_i on bowie_stuff(processed) pckree 0; Index created.
Can use zero sized unusable indexes to your advantage to index only useful porFons of a table. Most data here is processed:
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> select index_name, leaf_blocks from dba_indexes where index_name = 'BOWIE_STUFF_I'; INDEX_NAME LEAF_BLOCKS ------------------------------ ----------- BOWIE_STUFF_I 1877 SQL> select segment_name, blocks from dba_segments where segment_name = 'BOWIE_STUFF_I'; SEGMENT_NAME BLOCKS -------------------- ---------- BOWIE_STUFF_I 1920 SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=>'BOWIE_STUFF', esFmate_percent=>null, cascade=> true, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=>'BOWIE_STUFF', esFmate_percent=>null, method_opt=> 'FOR COLUMNS PROCESSED SIZE 5'); PL/SQL procedure successfully completed.
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> select * from bowie_stuff where processed = 'NO'; Execution Plan --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 40 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| BOWIE_STUFF | 5 | 40 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | BOWIE_STUFF_I | 5 | | 3 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 6 consistent gets 0 physical reads 0 redo size 540 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 5 rows processed
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values
SQL> drop index bowie_stuff_i; Index dropped. SQL> create index bowie_stuff_i on bowie_stuff(processed) 2 global parFFon by range (processed) 3 (parFFon not_processed_part values less than ('YES'), 4 parFFon processed_part values less than (MAXVALUE)) 5 unusable; Index created. SQL> alter index bowie_stuff_i rebuild parFFon not_processed_part; Index altered.
However in 11g R2, if we now recreate the index as a parFFoned index with only the “useful” porFon of the index usable ...
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values
SQL> select index_name, parFFon_name, leaf_blocks from dba_ind_parFFons where index_name = 'BOWIE_STUFF_I'; INDEX_NAME PARTITION_NAME LEAF_BLOCKS -------------------- -------------------- ----------- BOWIE_STUFF_I PROCESSED_PART 0 BOWIE_STUFF_I NOT_PROCESSED_PART 1 SQL> select segment_name, parFFon_name, blocks from dba_segments where segment_name = 'BOWIE_STUFF_I'; SEGMENT_NAME PARTITION_NAME BLOCKS -------------------- -------------------- ---------- BOWIE_STUFF_I NOT_PROCESSED_PART 8
We now only use a fracFon of the storage for the index and the “useful” porFon of the indexed data is just a single leaf block in size ...
The most comprehensive Oracle applications & technology content under one roof
4. Just index some column values SQL> select * from bowie_stuff where processed = 'NO'; Execution Plan -------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 45 | 1 (0)| 00:00:01 | | | | 1 | PARTITION RANGE SINGLE | | 5 | 45 | 1 (0)| 00:00:01 | 1 | 1 | | 2 | TABLE ACCESS BY INDEX ROWID| BOWIE_STUFF | 5 | 45 | 1 (0)| 00:00:01 | | | |* 3 | INDEX RANGE SCAN | BOWIE_STUFF_I | 5 | | 1 (0)| 00:00:01 | 1 | 1 | -------------------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 542 bytes sent via SQL*Net to client 395 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 5 rows processed
Note: The query itself is also more efficient with consistent gets reduced from 6 down to 4 ...
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity
• Generally indexes only process a “small” % of overall data
• Cheaper to use a Full Table Scan to process a “high” % of data
• Common belief that if returning more than ‘x’%, indexes are ignored by CBO
• However, indexes can be most efficient access path when returning anything up to 100% of data
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> create table ziggy (id number, name varchar2(30)); Table created. SQL> insert into ziggy select rownum, 'ZIGGY STARDUST' from dual connect by level <= 1000000; 1000000 rows created. SQL> commit; Commit complete. SQL> delete ziggy where id between 1 and 999000; 999000 rows deleted. SQL> commit; Commit complete. SQL> create index ziggy_id_i on ziggy(id); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=>'ZIGGY', cascade=> true, esFmate_percent=>null, method_opt=> 'FOR ALL COLUMNS SIZE 1');
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> select id from ziggy where id > 1; 1000 rows selected. Execution Plan ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000 | 5000 | 3 (0)| 00:00:01 | |* 1 | INDEX FAST FULL SCAN| ZIGGY_ID_I | 1000 | 5000 | 3 (0)| 00:00:01 | -----------------------------------------------------------------------------------
If all columns of interest can be found within an index, an Index Fast Full Scan can treat the index as a “skinny” table.
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> select * from ziggy where id > 1; 1000 rows selected. Execution Plan ------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)|Time | ------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1000 | 20000 | 8 (0)|00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| ZIGGY | 1000 | 20000 | 8 (0)|00:00:01 | |* 2 | INDEX RANGE SCAN | ZIGGY_ID_I | 1000 | | 4 (0)|00:00:01 | ------------------------------------------------------------------------------------------
However, because the table is so fragmented, the CBO is using the index to access the table and retrieve 100% of the rows ...
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> CREATE TABLE big_dwh_table_2 AS SELECT * FROM big_dwh_table ORDER BY total_sales; Table created. SQL> CREATE INDEX big_dwh_album_id_i ON big_dwh_table(album_id); Index created. SQL> CREATE INDEX big_dwh_2_album_id_i ON big_dwh_table_2(album_id); Index created. SQL> SELECT index_name, leaf_blocks, clustering_factor FROM user_indexes WHERE index_name like 'BIG_DWH%ALBUM_ID_I'; INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR ------------------------------ ----------- ----------------- BIG_DWH_2_ALBUM_ID_I 2090 990282 BIG_DWH_ALBUM_ID_I 2090 4957
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table WHERE album_id BETWEEN 10 AND 20 ORDER BY id; 1100 rows selected. Execution Plan ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows| Bytes |Cost(%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | |1200 | 36000 | 12 (9)|00:00:01 | | 1 | SORT ORDER BY | |1200 | 36000 | 12 (9)|00:00:01 | | 2 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE |1200 | 36000 | 11 (0)|00:00:01 | |* 3 | INDEX RANGE SCAN | BIG_DWH_ALBUM_ID_I |1200 | | 5 (0)|00:00:01 | ---------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 11 consistent gets 4 physical reads 0 redo size 30759 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory)
0 sorts (disk) 1100 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table WHERE album_id BETWEEN 10 AND 20 ORDER BY album_id; 1100 rows selected. Execution Plan ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost(%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1200 | 36000 | 11 (0)|00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE | 1200 | 36000 | 11 (0)|00:00:01| |* 2 | INDEX RANGE SCAN | BIG_DWH_ALBUM_ID_I | 1200 | | 5 (0)|00:00:01| ---------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 13 consistent gets 0 physical reads 0 redo size 30759 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1100 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table WHERE album_id BETWEEN 1 AND 10000 ORDER BY album_id; Execution Plan ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost(%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 28M|7145 (2)|00:00:48| | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE | 1000K| 28M|7145 (2)|00:00:48| |* 2 | INDEX FULL SCAN | BIG_DWH_ALBUM_ID_I | 1000K| |2130 (2)|00:00:15| --------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 139919 consistent gets 0 physical reads 0 redo size 37476458 bytes sent via SQL*Net to client 733722 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000000 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table ORDER BY album_id; 1000000 rows selected. Execution Plan -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 28M| | 7699 (4)| 00:00:51 | | 1 | SORT ORDER BY | | 1000K| 28M| 91M| 7699 (4)| 00:00:51 | | 2 | TABLE ACCESS FULL| BIG_DWH_TABLE | 1000K| 28M| | 555 (10)| 00:00:04 | -------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 43 recursive calls 5 db block gets 4954 consistent gets 5432 physical reads 0 redo size 37476485 bytes sent via SQL*Net to client 733722 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 1000000 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table WHERE album_id IS NOT NULL ORDER BY album_id; 1000000 rows selected. Execution Plan ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost(%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 28M|7145 (2)|00:00:48| | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE | 1000K| 28M|7145 (2)|00:00:48| |* 2 | INDEX FULL SCAN | BIG_DWH_ALBUM_ID_I | 1000K| |2130 (2)|00:00:15| ---------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 139919 consistent gets 7040 physical reads 0 redo size 37476493 bytes sent via SQL*Net to client 733722 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000000 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * FROM big_dwh_table_2 WHERE album_id IS NOT NULL ORDER BY album_id; 1000000 rows selected. Execution Plan ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 28M| | 7704 (4)| 00:00:51 | | 1 | SORT ORDER BY | | 1000K| 28M| 91M| 7704 (4)| 00:00:51 | |* 2 | TABLE ACCESS FULL| BIG_DWH_TABLE_2 | 1000K| 28M| | 561 (11)| 00:00:04 | ---------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 44 recursive calls 18 db block gets 4979 consistent gets 7669 physical reads 0 redo size 36721186 bytes sent via SQL*Net to client 733722 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 1 sorts (disk) 1000000 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT /*+ index(a) */ * from big_dwh_table_2 a WHERE album_id IS NOT NULL ORDER BY album_id; 1000000 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)|Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000K| 28M| 993K (1)|01:49:31 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE_2 | 1000K| 28M| 993K (1)|01:49:31 | |* 2 | INDEX FULL SCAN | BIG_DWH_2_ALBUM_ID_I | 1000K| | 2130 (2)|00:00:15 | ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1059553 consistent gets 7065 physical reads 0 redo size 36720213 bytes sent via SQL*Net to client 733722 bytes received via SQL*Net from client 66668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk)
1000000 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * from big_dwh_table_2 WHERE album_id BETWEEN 1 AND 15 ORDER BY album_id; 1500 rows selected. Execution Plan ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost(%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1500 | 45000 | 1297 (5)| 00:00:07| | 1 | SORT ORDER BY | | 1500 | 45000 | 1297 (5)| 00:00:07| |* 2 | TABLE ACCESS FULL| BIG_DWH_TABLE_2 | 1500 | 45000 | 1296 (4)| 00:00:07| ------------------------------------------------------------------------------------ Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4981 consistent gets 0 physical reads 0 redo size 54139 bytes sent via SQL*Net to client 1485 bytes received via SQL*Net from client 101 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 1500 rows processed
The most comprehensive Oracle applications & technology content under one roof
5. 100% Index SelecFvity SQL> SELECT * from big_dwh_table_2 WHERE album_id BETWEEN 1 AND 13 ORDER BY album_id; 1300 rows selected. Execution Plan ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost(%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1300| 39000 |1295 (1)| 00:00:07 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_DWH_TABLE_2 | 1300| 39000 |1295 (1)| 00:00:07 | |* 2 | INDEX RANGE SCAN | BIG_DWH_2_ALBUM_ID_I | 1300| | 5 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1385 consistent gets 60 physical reads 0 redo size 47048 bytes sent via SQL*Net to client 1342 bytes received via SQL*Net from client 88 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1300 rows processed
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2
SQL> create table major_tom (id number, code number, name varchar2(30)); Table created. SQL> create index major_tom_i on major_tom(id); Index created. SQL> insert into major_tom select rownum, mod(rownum,100), 'GROUND CONTROL' from dual connect by level <=336000; 336000 rows created. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=> 'MAJOR_TOM', cascade=> true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> select blevel, leaf_blocks, num_rows, clustering_factor from dba_indexes wh ere index_name='MAJOR_TOM_I'; BLEVEL LEAF_BLOCKS NUM_ROWS CLUSTERING_FACTOR ---------- ----------- ---------- ----------------- 1 671 336000 1296
Careful of indexes increasing from BLEVEL 1 to 2. Following demo 11.2.0.1 with 8K block size.
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2 SQL> select * from major_tom where id = 42; Execution Plan ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 23 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| MAJOR_TOM | 1 | 23 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | MAJOR_TOM_I | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 531 bytes sent via SQL*Net to client 395 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 6 sorts (memory) 0 sorts (disk) 1 rows processed
When BLEVEL = 1, CBO ignores the BLEVEL from its calculaFons
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2
SQL> create table ziggy (id number, code number, name varchar2(30)); Table created. SQL> insert into ziggy select rownum, mod(rownum,10000), 'ZIGGY STARDUST' from dual connect by level <= 1000000; 1000000 rows created. SQL> commit; Commit complete. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=> 'ZIGGY', cascade=> true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
Let’s create another table, with a CODE column that’s has 100 occurrences of each value ...
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2 SQL> select * from ziggy z, major_tom m where z.id = m.id and z.code in (42, 4242); 68 rows selected. Execution Plan -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 200 | 9400 | 1372 (2)| 00:00:17 | | 1 | NESTED LOOPS | | | | | | | 2 | NESTED LOOPS | | 200 | 9400 | 1372 (2)| 00:00:17 | |* 3 | TABLE ACCESS FULL | ZIGGY | 200 | 4800 | 1105 (2)| 00:00:14 | |* 4 | INDEX RANGE SCAN | MAJOR_TOM_I | 1 | | 1 (0)| 00:00:01 | | 5 | TABLE ACCESS BY INDEX ROWID| MAJOR_TOM | 1 | 23 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------------------------
CBO choose a Nest Loop as the cost of accessing the MAJOR_TOM table 100 Fmes via the index is relaFvely inexpensive
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2
SQL> insert into major_tom select rownum+336000, mod(rownum,100), 'GROUND CONTROL' from dual connect by level <=500; 500 rows created. SQL> commit; Commit complete. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=> 'MAJOR_TOM', cascade=> true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> select blevel, leaf_blocks, num_rows, clustering_factor from dba_indexes where index_name = 'MAJOR_TOM_I'; BLEVEL LEAF_BLOCKS NUM_ROWS CLUSTERING_FACTOR ---------- ----------- ---------- ----------------- 2 672 336500 1298
Let’s just add another 500 rows (or 0.15% of data)
Leaf blocks has just gone up by just 1 but it’s enough to increase the BLEVEL to 2 ...
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2 SQL> select * from major_tom where id = 42; Execution Plan ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 23 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| MAJOR_TOM | 1 | 23 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | MAJOR_TOM_I | 1 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 5 consistent gets 0 physical reads 0 redo size 531 bytes sent via SQL*Net to client 395 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
The cost of accessing 1 row has gone up by 2 as the BLEVEL is no longer ignored by the CBO ...
The most comprehensive Oracle applications & technology content under one roof
6. BLEVEL 1 => BLEVEL 2 SQL> select * from ziggy z, major_tom m where m.id = z.id and z.code in (42, 4242); 68 rows selected. Execution Plan -------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 200 | 9400 | 1485 (2)| 00:00:18 | |* 1 | HASH JOIN | | 200 | 9400 | 1485 (2)| 00:00:18 | |* 2 | TABLE ACCESS FULL| ZIGGY | 200 | 4800 | 1105 (2)| 00:00:14 | | 3 | TABLE ACCESS FULL| MAJOR_TOM | 336K| 7558K| 378 (1)| 00:00:05 | --------------------------------------------------------------------------------
Nested Loop costs (previously 1372) have gone up considerably as the cost has gone up by 2 for each iteraFon of the loop. Hash Join and FTS now selected by the CBO even though overall size of index is similar. Index BLEVEL can potenFally toggle between 1 and 2 and result in variable performance if size is borderline and index frequently rebuilt due to some deleFons.
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer
• Since Oracle8, Non-‐Unique Indexes Can Police Primary and Unique Key Constraints
• Non-‐Unique Indexes Necessary For Deferrable and NoValidate Constraints
• Non-‐Unique Indexes are not automaFcally dropped when constraint is dropped or disabled
• However, Unique Indexes are not outdated and are generally “beyer” ...
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer SQL> CREATE TABLE index_size_test (id NUMBER, value VARCHAR2(100) CONSTRAINT index_size_test_pk_1 PRIMARY KEY); Table created. SQL> INSERT INTO index_size_test VALUES (1, 'DAVID BOWIE'); 1 row created. SQL> COMMIT; Commit complete. SQL> SELECT header_file, header_block+1 FROM dba_segments WHERE segment_name = 'INDEX_SIZE_TEST_PK_1'; HEADER_FILE HEADER_BLOCK+1 ----------- -------------- 5 138370 SQL> ALTER SYSTEM DUMP DATAFILE 5 BLOCK 138370; System altered.
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer row#0[8016] flag: -‐-‐-‐-‐-‐-‐, lock: 2, len=20, data:(6): 01 42 1c 7a 00 00 col 0; len 11; (11): 44 41 56 49 44 20 42 4f 57 49 45
SQL> ALTER TABLE index_size_test DROP PRIMARY KEY; Table altered. SQL> CREATE INDEX index_size_test_pk_2 ON index_size_test(value); Index created. SQL> ALTER TABLE index_size_test ADD PRIMARY KEY (value); Table altered.
row#0[8015] flag: -‐-‐-‐-‐-‐-‐, lock: 0, len=21 col 0; len 11; (11): 44 41 56 49 44 20 42 4f 57 49 45 col 1; len 6; (6): 01 42 1c 7a 00 00
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer SQL> CREATE TABLE test1 AS SELECT rownum id FROM dual CONNECT BY LEVEL <= 1000000; Table created. SQL> CREATE INDEX non_unique_idx ON test1(id) PCTFREE 0; Index created. SQL> CREATE TABLE test2 AS SELECT rownum id FROM dual CONNECT BY LEVEL <= 1000000; Table created. SQL> CREATE UNIQUE INDEX non_unique_idx ON test2(id) PCTFREE 0; Index created. SQL> exec dbms_stats.gather_index_stats(ownname=>'BOWIE', indname=> 'NON_UNIQUE_IDX', esFmate_percent=> null); PL/SQL procedure successfully completed. SQL> exec dbms_stats.gather_index_stats(ownname=>'BOWIE', indname=>'UNIQUE_IDX', esFmate_percent=> null); PL/SQL procedure successfully completed.
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer
SQL> SELECT index_name, blevel, leaf_blocks, num_rows FROM dba_indexes WHERE index_name IN ('NON_UNIQUE_IDX', 'UNIQUE_IDX'); INDEX_NAME BLEVEL LEAF_BLOCKS NUM_ROWS -------------- ------ ----------- -------- NON_UNIQUE_IDX 2 1999 1000000 UNIQUE_IDX 2 1875 1000000
The Non-‐Unique Index has used an extra 124 blocks or approximately 6.6% more blocks than the Unique Index
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer
SQL> CREATE TABLE ziggy (id NUMBER, name VARCHAR2(30)); Table created. SQL> INSERT into ziggy SELECT rownum, 'Bowie' FROM dual CONNECT BY level <=1000; 1000 rows created. SQL> COMMIT; Commit complete. SQL> ALTER TABLE ziggy ADD PRIMARY KEY (id); Table altered. SQL> exec dbms_stats.gather_table_stats(ownname=>NULL, tabname=>'ZIGGY', esFmate_percent=> NULL, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
Unique indexes have less latching and CPU related overheads ...
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer In one session, run the following a couple of Fmes to ensure no recursive SQL: SQL> SELECT * FROM ziggy WHERE id = 10; ID NAME ----- ----------------------- 10 Bowie
In other session, run the following (where SID = the sid of the other session) before and a�er an execuFon of the above select statement in the other session.
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# = n.staFsFc# AND s.sid = 123 AND n.name like 'consistent%';
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer NAME VALUE -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ consistent gets 17703 consistent gets from cache 17703 consistent gets -‐ examinaFon 10536
NAME VALUE -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ consistent gets 17706 consistent gets from cache 17706 consistent gets -‐ examinaFon 10539
Note that consistent gets increases by 3, consistent gets from cache increases by 3, consistent gets -‐ examinaFon increases by 3 (1 for the index root block, 1 for the index leaf block and 1 for the table block). That’s a total of 3 CRs and 3 latches (as all CRs are the cheaper ‘examinaFons’ which only require 1 latch each)
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer Now the same test but this Fme with a non-‐unique index ...
SQL> ALTER TABLE ziggy DROP PRIMARY KEY; Table altered. SQL> ALTER TABLE ziggy ADD PRIMARY KEY (id) USING INDEX 2 (CREATE INDEX ziggy_id_i ON ziggy(id)); Table altered. SQL> exec dbms_stats.gather_table_stats(ownname=>NULL, tabname=>'ZIGGY', esFmate_percent=> NULL, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer NAME VALUE -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ consistent gets 18504 consistent gets from cache 18504 consistent gets -‐ examinaFon 10949
NAME VALUE -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ consistent gets 18508 consistent gets from cache 18508 consistent gets -‐ examinaFon 10950
Note that consistent gets increases by 4 (not 3), consistent gets from cache increases by 4 (not 3), consistent gets -‐ examinaFon increases by only 1(not 3). In summary, there are 4 not 3 CRs with only the root block acquired via a 1 latch examinaFon CR, the other 3 CRs are ‘full’ 2 latch gets which is a total of 7 latches (vs. only 3 for the equivalent unique index).
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer
SQL> create table radiohead (id number constraint radiohead_pk_i primary key using index (create unique index radiohead_pk_i on radiohead(id)), name varchar2(20)); Table created. SQL> select index_name, uniqueness, table_name from dba_indexes where index_name = 'RADIOHEAD_PK_I'; INDEX_NAME UNIQUENES TABLE_NAME ------------------------------ --------- ------------------------------ RADIOHEAD_PK_I UNIQUE RADIOHEAD SQL> insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 10; 10 rows created. SQL> commit; Commit complete. SQL> insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12; insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12 * ERROR at line 1: ORA-‐00001: unique constraint (BOWIE.RADIOHEAD_PK_I) violated
Create table with a PK and ayempt to insert duplicate PK rows ...
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer SQL> insert /*+ ignore_row_on_dupkey_index(radiohead,radiohead_pk_i) */ into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12; 2 rows created. SQL> insert /*+ ignore_row_on_dupkey_index(radiohead(id)) */ into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 13; 1 row created. SQL> commit; Commit complete. SQL> select * from radiohead; ID NAME ---------- -------------------- 1 OK COMPUTER 2 OK COMPUTER 3 OK COMPUTER 4 OK COMPUTER 5 OK COMPUTER 6 OK COMPUTER 7 OK COMPUTER 8 OK COMPUTER 9 OK COMPUTER 10 OK COMPUTER 11 OK COMPUTER 12 OK COMPUTER 13 OK COMPUTER 13 rows selected.
With the new Oracle11g Rel. 1 hint, duplicate violaFon rows are automaFcally ignored.
The most comprehensive Oracle applications & technology content under one roof
7. Why Unique Indexes Are Beyer
SQL> create table radiohead (id number constraint radiohead_pk_i primary key using index (create index radiohead_pk_i on radiohead(id)), name varchar2(20)); Table created. SQL> insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 10; 10 rows created. SQL> commit; Commit complete. SQL> select index_name, uniqueness, table_name from dba_indexes where index_name='RADIOHEAD_PK_I'; INDEX_NAME UNIQUENES TABLE_NAME ------------------------------ --------- ------------------------------ RADIOHEAD_PK_I NONUNIQUE RADIOHEAD SQL> insert /*+ ignore_row_on_dupkey_index(radiohead,radiohead_pk_i) */ into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12; insert /*+ ignore_row_on_dupkey_index(radiohead,radiohead_pk_i) */ into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12 * ERROR at line 1: ORA-‐38913: Index specified in the index hint is invalid
The index must be Unique for the hint to be valid ...
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index
• A common percepFon that it’s pointless indexing a small table
• Cost of accessing both index and table would exceed just accessing the table
• Of course small tables might sFll require indexes associated with PK and Unique Key constraint
• However, any index can be accessed directly via the root block whereas tables need to be accessed first via the segment header
• Index accesses have less associated latch overheads
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index
SQL> CREATE TABLE small AS SELECT rownum id, 'BOWIE' name FROM dual CONNECT BY LEVEL <= 100; Table created. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>'SMALL', esFmate_percent=> null, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> SELECT blocks from user_tables WHERE table_name='SMALL'; BLOCKS ---------- 1
Create a Fny table with just 100 rows that fits in one table data block
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> SELECT * FROM small WHERE id = 42; ID NAME ---------- ----- 42 BOWIE Execution Plan --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 2 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| SMALL | 1 | 9 | 2 (0)| 00:00:01 | --------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 465 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory)
0 sorts (disk) 1 rows processed
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index In another session, run the following query before and a�er the query in the first session (where 134 is the SID from the first session):
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 275851 consistent gets from cache 275851 consistent gets - examination 70901
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%';
NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 275855 (+4) consistent gets from cache 275855 (+4) consistent gets - examination 70901 (0)
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> CREATE INDEX small_i ON small(id) COMPUTE STATISTICS; Index created. SQL> SELECT * FROM small WHERE id = 42; Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| SMALL | 1 | 9 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | SMALL_I | 1 | | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 3 consistent gets 0 physical reads 0 redo size 465 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client
0 sorts (memory) 0 sorts (disk) 1 rows processed
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 276630 consistent gets from cache 276629 consistent gets - examination 71063
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 276633 (+3) consistent gets from cache 276632 (+3) consistent gets - examination 71063 (0)
Note: Again, the number of consistent gets has shown to be 3, 1 less than the Full Table Scan, with none of them consistent gets – examinaFons ...
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> CREATE UNIQUE INDEX small_i ON small(id) COMPUTE STATISTICS; Index created. SQL> SELECT * FROM small WHERE id = 42; Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| SMALL | 1 | 9 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | SMALL_I | 1 | | 0 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 0 physical reads 0 redo size 465 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 17255 consistent gets from cache 17255 consistent gets - examination 4050
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 17257 (+2) consistent gets from cache 17257 (+2) consistent gets - examination 4052 (+2)
Note: Not only has consistent reads dropped down to just 2, but both are the cheaper consistent gets – examinaFon (2 latches vs. 8 latches for Full Table Scan)
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> create table small (id number primary key, name varchar2(10)) organizaFon index; Table created. SQL> insert into small select rownum, 'BOWIE' from dual connect by level <=100; 100 rows created. SQL> commit; Commit complete. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>'SMALL', method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
And if we now create the small table as an Index Organized Table ...
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> SELECT * FROM small WHERE id = 42; Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 0 (0)| 00:00:01 | | 1 | INDEX UNIQUE SCAN| SYS_IOT_TOP_67877 | 1 | 9 | 0 (0)| 00:00:01 | --------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1 consistent gets 0 physical reads 0 redo size 465 bytes sent via SQL*Net to client 396 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Note: There’s no need to visit a table segment, so the Unique Index has further decreased the number of consistent reads down to just 1 ...
The most comprehensive Oracle applications & technology content under one roof
8. No Table Is Too Small To Index SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 32842 consistent gets from cache 32842 consistent gets - examination 6694
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.staFsFc# =n.staFsFc# AND s.sid = 134 AND n.name LIKE 'consistent%'; NAME VALUE ---------------------------------------------------------------- ---------- consistent gets 32843 (+1) consistent gets from cache 32843 (+1) consistent gets - examination 6695 (+1)
Note: Consistent gets has dropped down further to just 1, and it’s the cheaper consistent gets – examinaFon (1 latch vs. 8 latches for Full Table Scan)
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Changes Without Changing StaFsFcs
• Common held belief that an execuFon plan won’t change providing staFsFcs don’t
• Too keep an environment “stable”, don’t change staFsFcs too o�en
• If everything currently runs efficiently, no need to regather staFsFcs as all is already OK
• However, updaFng staFsFcs someFmes necessary to ensure environment remains stable
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes SQL> create table muse (id number, muse_date date, name varchar2(10)); Table created. SQL> declare 2 v_count number; 3 begin 4 v_count:=0; 5 for i in 1..1830 loop 6 for j in 1..1000 loop 7 v_count:= v_count+1; 8 insert into muse values (v_count, sysdate-‐i, 'MUSE'); 9 end loop; 10 end loop; 11 commit; 12 end; 13 / PL/SQL procedure successfully completed. SQL> create index muse_i on muse(muse_date); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=>'MUSE', cascade=>true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
Start by adding about 5 years worth of data ...
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes
SQL> select * from muse where muse_date > sysdate -‐ 365; 364000 rows selected. Execution Plan -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 363K| 6390K| 1330 (11)| 00:00:07 | |* 1 | TABLE ACCESS FULL| MUSE | 363K| 6390K| 1330 (11)| 00:00:07 | --------------------------------------------------------------------------
Let’s now query for the last years worth of data ...
CBO chooses a Full Table Scan as a large % (approx. 20%) of data is being selected. Note: StaFsFcs are currently 100% accurate and the CBO gets its cardinality esFmates almost spot on.
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes SQL> declare 2 v_count number; 3 begin 4 v_count:=1830000; 5 for i in 1..365 loop 6 for j in 1..1000 loop 7 v_count:= v_count+1; 8 insert into muse values (v_count, sysdate+i, 'MUSE'); 9 end loop; 10 end loop; 11 commit; 12 end; 13 / PL/SQL procedure successfully completed.
We now add another year’s worth of data ...
But we don’t collect any new staFsFcs ...
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes
SQL> select * from muse where muse_date > (sysdate+365) -‐ 365; 365000 rows selected. Execution Plan -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 944 | 16992 | 9 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| MUSE | 944 | 16992 | 9 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | MUSE_I | 944 | | 5 (0)| 00:00:01 | --------------------------------------------------------------------------------------.
We now simulate it being a year later by adding a year to the sysdate ...
The CBO has now switched to suddenly using an index. Note the CBO has now got the rows esFmate all wrong ...
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes • Although the staFsFcs haven’t changed: – Time has changed (it’s now a year later) – The data has changed (we now have a years more)
• The CBO now has data values beyond the maximum known value and adjusts costs accordingly
• We need to update the staFsFcs in order for the plan not to change ...
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes
SQL> exec dbms_stats.gather_table_stats(ownname=>'BOWIE', tabname=>'MUSE', cascade=>true, esFmate_percent=>null, method_opt=>'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> select * from muse where muse_date > (sysdate+365) -‐ 365; 365000 rows selected. Execution Plan -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 364K| 6413K| 1652 (14)| 00:00:09 | |* 1 | TABLE ACCESS FULL| MUSE | 364K| 6413K| 1652 (14)| 00:00:09 | --------------------------------------------------------------------------
If we now collect current staFsFcs ...
The CBO now returns to the more efficient Full Table Scan. The CBO has now got the rows esFmate almost spot on again.
The most comprehensive Oracle applications & technology content under one roof
9. Index Plan Suddenly Changes • So keeping staFsFcs accurate is important for stable plans
• Plans can suddenly change without changing staFsFcs. EG: – Based on dynamic sampling se�ngs – Evolved baselines