Database & Technology 2 _ Richard Foote _ 10 things you probably dont know about Oracle's...

91
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

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  

The most comprehensive Oracle applications & technology content under one roof

10.  Best  Oracle  Index  Website  J  richardfoote.wordpress.com