zoukankan      html  css  js  c++  java
  • Oracle学习笔记索引执行计划中的关键字(十三)

    INDEX RANGE SCAN:索引范围查询

    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    create  index idx_object_id on t(object_id);
    set autotrace traceonly
    set linesize 1000
    exec dbms_stats.gather_table_stats(ownname => 'LJB',tabname => 'T',estimate_percent => 10,method_opt=> 'for all indexed columns',cascade=>TRUE) ;  
    
    select * from t where object_id=8;
    执行计划
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               |     1 |   207 |     2   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |   207 |     2   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_OBJECT_ID |     1 |       |     1   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              4  consistent gets
              0  physical reads
              0  redo size
           1394  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    

      INDEX UNIQUE SCAN:索引唯一性查询

    --请注意这个INDEX UNIQUE SCAN扫描方式,在唯一索引情况下使用。
    
    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    create unique index idx_object_id on t(object_id);
    set autotrace traceonly
    set linesize 1000
    
    select * from t where object_id=8;
    执行计划
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               |     1 |   207 |     2   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |   207 |     2   (0)| 00:00:01 |
    |*  2 |   INDEX UNIQUE SCAN         | IDX_OBJECT_ID |     1 |       |     1   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------|
    统计信息
    ---------------------------------------------------
              0  recursive calls
              0  db block gets
              3  consistent gets
              0  physical reads
              0  redo size
           1298  bytes sent via SQL*Net to client
            404  bytes received via SQL*Net from client
              1  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    TABLE ACCESS BY USER ROWID:索引rowid查询

    --请注意这个TABLE ACCESS BY USER ROWID扫描方式,直接根据rowid来访问,最快的访问方式!
    
    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    --注意,这里连索引都没建!
    --create  index idx_object_id on t(object_id);
    set autotrace off
    select rowid from t where object_id=8; 
    ROWID
    ------------------
    AAAZxiAAGAAAB07AAH
    
    set autotrace traceonly
    set linesize 1000
    
    select * from t where object_id=8 and rowid='AAAZxiAAGAAAB07AAH';
    执行计划
    -----------------------------------------------------------------------------------
    | Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT           |      |     1 |   219 |     1   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS BY USER ROWID| T    |     1 |   219 |     1   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              1  consistent gets
              0  physical reads
              0  redo size
           1391  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    INDEX FULL SCAN:索引全扫描

    ---请记住这个INDEX FULL SCAN扫描方式,并体会与INDEX FAST FULL SCAN的区别
    
    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    alter table T modify object_id not null;
    create  index idx_object_id on t(object_id);
    set autotrace traceonly
    set linesize 1000
    
    select * from t  order by object_id;
    执行计划
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               | 88780 |    17M|  1208   (1)| 00:00:15 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T             | 88780 |    17M|  1208   (1)| 00:00:15 |
    |   2 |   INDEX FULL SCAN           | IDX_OBJECT_ID | 88780 |       |   164   (1)| 00:00:02 |
    ---------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          10873  consistent gets
              0  physical reads
              0  redo size
        8116181  bytes sent via SQL*Net to client
          54040  bytes received via SQL*Net from client
           4877  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
          73130  rows processed

    INDEX FAST FULL SCAN:索引min和max全扫描

    --请注意这个INDEX FULL SCAN (MIN/MAX)扫描方式
    
    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    create  index idx_object_id on t(object_id);
    set autotrace traceonly
    set linesize 1000
    
    select max(object_id) from t;
    执行计划
    --------------------------------------------------------------------------------------------
    | Id  | Operation                  | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT           |               |     1 |    13 |     2   (0)| 00:00:01 |
    |   1 |  SORT AGGREGATE            |               |     1 |    13 |            |          |
    |   2 |   INDEX FULL SCAN (MIN/MAX)| IDX_OBJECT_ID |     1 |    13 |     2   (0)| 00:00:01 |
    --------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              2  consistent gets
              0  physical reads
              0  redo size
            431  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    INDEX FULL SCAN (MINMAX):索引快速全扫描

    ---请记住这个INDEX FAST FULL SCAN扫描方式,并体会与INDEX FULL SCAN的区别
    
    drop table t purge;
    create table t as select * from dba_objects ;
    update t set object_id=rownum;
    commit;
    alter table T modify object_id not null;
    create  index idx_object_id on t(object_id);
    set autotrace traceonly
    set linesize 1000
    
    select count(*) from t;
    执行计划
    -------------------------------------------------------------------------------
    | Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |               |     1 |    49   (0)| 00:00:01 |
    |   1 |  SORT AGGREGATE       |               |     1 |            |          |
    |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_ID | 88780 |    49   (0)| 00:00:01 |
    -------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            170  consistent gets
              0  physical reads
              0  redo size
            425  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    INDEX SKIP SCAN:跳跃索引

    --请记住这个INDEX SKIP SCAN扫描方式
    
    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_type='TABLE' ;
    commit;
    update t set object_type='VIEW' where rownum<=30000;
    commit;
    create  index idx_type_id on t(object_type,object_id);
    exec dbms_stats.gather_table_stats(ownname => 'LJB',tabname => 'T',estimate_percent => 10,method_opt=> 'for all indexed columns',cascade=>TRUE) ;  
    set autotrace traceonly
    set linesize 1000
    select * from t where object_id=8;
    
    执行计划
    -------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |             |     1 |    94 |     4   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T           |     1 |    94 |     4   (0)| 00:00:01 |
    |*  2 |   INDEX SKIP SCAN           | IDX_TYPE_ID |     1 |       |     3   (0)| 00:00:01 |
    -------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - access("OBJECT_ID"=8)
           filter("OBJECT_ID"=8)
    
    
    统计信息
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              7  consistent gets
              0  physical reads
              0  redo size
           1401  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    TABLE ACCESS BY INDEX ROWID:回表

    drop table t purge;
    create table t as select * from dba_objects;
    update t set object_id=rownum;
    commit;
    create  index idx_object_id on t(object_id);
    set autotrace traceonly explain
    set linesize 1000
    
    select object_id from t where object_id=2 and object_type='TABLE';
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               |     9 |   216 |     2   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS BY INDEX ROWID| T             |     9 |   216 |     2   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_OBJECT_ID |    12 |       |     1   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------
    
    
    TABLE ACCESS BY INDEX ROWID消失了。
    create  index idx_id_type on t(object_id,object_type);
    select object_id from t where object_id=2 and object_type='TABLE';
    
    执行计划
    --------------------------------------------------------------------------------
    | Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT |             |     9 |   216 |     1   (0)| 00:00:01 |
    |*  1 |  INDEX RANGE SCAN| IDX_ID_TYPE |     9 |   216 |     1   (0)| 00:00:01 |
    --------------------------------------------------------------------------------
    

      索引的不足之处:

    1.索引的各种开销,访问开销,更新开销,建立开销

    2.索引使用失效:逻辑失效,物理失效

    索引访问开销-索引访问集中,导致热块的竞争

    /*  
      结论:一般来说,由于用户都是访问最新产生的数据的,所以容易产生索引的热点块竞争,
      这也算是索引的开销的弊端了,不过这时可以考虑用方向键索引来将索引的位置转移到不同的地方,只是反向键索引要担心范围查询无法使用,这在案例
      的部分会有描述。
      
    */
    
    drop table t purge;
    create table t (id number,deal_date date,area_code number,nbr number,contents varchar2(4000));
    insert into t(id,deal_date,area_code,nbr,contents)
          select rownum,
                 to_date( to_char(sysdate-365,'J')+TRUNC(DBMS_RANDOM.VALUE(0,700)),'J'),
                 ceil(dbms_random.value(590,599)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 rpad('*',400,'*')
            from dual
          connect by rownum <= 100000;
    commit;
    
    create index idx_t_id on t(id);
    
    --由于一般来说,最新的值都是最新产生的,所以访问容易产生热快竞争。
    因为select * from t where id=100000;
        select * from t where id=99999;
        select * from t where id=99998;
        select * from t where id=99997;
    ---数据是相邻的,很可能在同一个索引块上,所以很容易产生热点索引块竞争。
        
    --如果方向键索引,刚才的语句等于变成如此查询:    
        select * from t where id=000001;
        select * from t where id=99999;
        select * from t where id=89999;
        select * from t where id=79999;
    --他们都被分到很远的地方,不会在同一个索引块上,有效的避免了索引块竞争。
    drop index idx_t_id ;
    create index idx_t_id on t(id) reverse;
    

      索引访问开销-回表性能取决于聚合因子:CLUSTERING_FACTOR

    /*
      结论:索引查询要尽可能的避免回表,如果不可避免,需要关注聚合因子是否过大。
    */
    
    ---两者性能差异显著,ORGANIZED表的聚合因子比较小,回表的代价较低,如下,产生2900个BUFFER
    select /*+ index( colocated colocated_pk ) */ * from colocated where x between 20000 and 40000;
    SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL,'runstats_last'));
    
    ------------------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
    ------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |              |      1 |        |  20001 |00:00:00.05 |    2900 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| COLOCATED    |      1 |  20002 |  20001 |00:00:00.05 |    2900 |
    |*  2 |   INDEX RANGE SCAN          | COLOCATED_PK |      1 |  20002 |  20001 |00:00:00.03 |    1375 |
    ------------------------------------------------------------------------------------------------------
    
    ---两者性能差异显著,DISORGANIZED表的聚合因子比较大,回表的代价很高,如下,产生21360个BUFFER
    select /*+ index( disorganized disorganized_pk ) */* from disorganized  where x between 20000 and 40000;
    SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL,'runstats_last'));
    
    ---------------------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
    ---------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |                 |      1 |        |  20001 |00:00:00.09 |   21360 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| DISORGANIZED    |      1 |  20002 |  20001 |00:00:00.09 |   21360 |
    |*  2 |   INDEX RANGE SCAN          | DISORGANIZED_PK |      1 |  20002 |  20001 |00:00:00.03 |    1375 |
    ---------------------------------------------------------------------------------------------------------
    
    select a.table_name,
           a.index_name,
           a.blevel,
           a.leaf_blocks,
           b.num_rows,
           b.blocks,
           a.clustering_factor,
           trunc(a.clustering_factor / b.num_rows,2) cluster_rate
      from user_indexes a, user_tables b
     where a.table_name = b.table_name
         and index_name in ('COLOCATED_PK', 'DISORGANIZED_PK' )
         and a.clustering_factor is not null
     order by cluster_rate desc;
    
    TABLE_NAME         INDEX_NAME          BLEVEL LEAF_BLOCKS   NUM_ROWS     BLOCKS CLUSTERING_FACTOR CLUSTER_RATE
    ------------------ -------------------------- ----------- ---------- ---------- ----------------- ------------
    DISORGANIZED       DISORGANIZED_PK          1         208     100000       1219             99927          .99
    COLOCATED          COLOCATED_PK             1         208     100000       1252              1190          .01
    

      索引更新开销_分区表更新(无索引比较):

    /*
      结论:无索引的情况下,分区表和普通表的插入相比,分区表的开销更大,会插的更慢,
      因为数据需要判断落在哪个分区,需要额外的开销。
    */
    
    --构造分区表,插入数据。
    drop table range_part_tab purge;
    create table range_part_tab (id number,deal_date date,area_code number,nbr1 number,nbr2 number,nbr3 number,contents varchar2(4000))
               partition by range (deal_date)
               (
               partition p_201301 values less than (TO_DATE('2013-02-01', 'YYYY-MM-DD')),
               partition p_201302 values less than (TO_DATE('2013-03-01', 'YYYY-MM-DD')),
               partition p_201303 values less than (TO_DATE('2013-04-01', 'YYYY-MM-DD')),
               partition p_201304 values less than (TO_DATE('2013-05-01', 'YYYY-MM-DD')),
               partition p_201305 values less than (TO_DATE('2013-06-01', 'YYYY-MM-DD')),
               partition p_201306 values less than (TO_DATE('2013-07-01', 'YYYY-MM-DD')),
               partition p_201307 values less than (TO_DATE('2013-08-01', 'YYYY-MM-DD')),
               partition p_201308 values less than (TO_DATE('2013-09-01', 'YYYY-MM-DD')),
               partition p_201309 values less than (TO_DATE('2013-10-01', 'YYYY-MM-DD')),
               partition p_201310 values less than (TO_DATE('2013-11-01', 'YYYY-MM-DD')),
               partition p_201311 values less than (TO_DATE('2013-12-01', 'YYYY-MM-DD')),
               partition p_201312 values less than (TO_DATE('2014-01-01', 'YYYY-MM-DD')),
               partition p_201401 values less than (TO_DATE('2014-02-01', 'YYYY-MM-DD')),
               partition p_201402 values less than (TO_DATE('2014-03-01', 'YYYY-MM-DD')),
               partition p_201403 values less than (TO_DATE('2014-04-01', 'YYYY-MM-DD')),
               partition p_201404 values less than (TO_DATE('2014-05-01', 'YYYY-MM-DD')),
               partition p_max values less than (maxvalue)
               )
               ;
    
    --以下是插入2013年一整年日期随机数和表示福建地区号含义(591到599)的随机数记录,共有100万条,如下:
    insert into range_part_tab (id,deal_date,area_code,nbr1,nbr2,nbr3,contents)
          select rownum,
                 to_date( to_char(sysdate-365,'J')+TRUNC(DBMS_RANDOM.VALUE(0,365)),'J'),
                 ceil(dbms_random.value(591,599)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 rpad('*',400,'*')
            from dual
          connect by rownum <= 2000000;
    commit;
    
    
    --以下是插入2014年部分日期随机数和表示福建地区号含义(591到599)的随机数记录,共有20万条,如下:
    insert into range_part_tab (id,deal_date,area_code,nbr1,nbr2,nbr3,contents)
          select rownum,
                 to_date( to_char(sysdate,'J')+TRUNC(DBMS_RANDOM.VALUE(0,60)),'J'),
                 ceil(dbms_random.value(591,599)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 rpad('*',400,'*')
            from dual
          connect by rownum <= 400000;
    commit;
    /*
    create index idx_parttab_id on range_part_tab(id) local;
    create index idx_parttab_nbr1 on range_part_tab(nbr1) local;
    create index idx_parttab_nbr2 on range_part_tab(nbr2) local;
    create index idx_parttab_nbr3 on range_part_tab(nbr3) local;
    create index idx_parttab_area on range_part_tab(area_code) local;
    */
    
    drop table normal_tab purge;
    create table normal_tab (id number,deal_date date,area_code number,nbr1 number,nbr2 number,nbr3 number,contents varchar2(4000));
    insert into normal_tab select * from range_part_tab;
    commit;
    /*
    create index idx_tab_id on normal_tab(id) ;
    create index idx_tab_nbr1 on normal_tab(nbr1) ;
    create index idx_tab_nbr2 on normal_tab(nbr2) ;
    create index idx_tab_nbr3 on normal_tab(nbr3) ;
    create index idx_tab_area on normal_tab(area_code) ; 
    */
    
    select count(*) from normal_tab where deal_date>=TO_DATE('2014-02-01', 'YYYY-MM-DD') and deal_date<TO_DATE('2014-03-01', 'YYYY-MM-DD');
    select count(*) from range_part_tab where deal_date>=TO_DATE('2014-02-01', 'YYYY-MM-DD') and deal_date<TO_DATE('2014-03-01', 'YYYY-MM-DD');
    
    
    set timing on 
    insert into range_part_tab 
         select rownum,
                 to_date( to_char(sysdate+60,'J')+TRUNC(DBMS_RANDOM.VALUE(0,60)),'J'),
                 ceil(dbms_random.value(591,599)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 rpad('*',400,'*')
            from dual
          connect by rownum <= 400000;
    commit;
    
    insert into normal_tab 
         select rownum,
                 to_date( to_char(sysdate+60,'J')+TRUNC(DBMS_RANDOM.VALUE(0,60)),'J'),
                 ceil(dbms_random.value(591,599)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 ceil(dbms_random.value(18900000001,18999999999)),
                 rpad('*',400,'*')
            from dual
          connect by rownum <= 400000;
    commit;
    
    
    SQL> set timing on
    SQL> insert into range_part_tab
      2       select rownum,
      3               to_date( to_char(sysdate+60,'J')+TRUNC(DBMS_RANDOM.VALUE(0,60)),'J'),
      4               ceil(dbms_random.value(591,599)),
      5               ceil(dbms_random.value(18900000001,18999999999)),
      6               ceil(dbms_random.value(18900000001,18999999999)),
      7               ceil(dbms_random.value(18900000001,18999999999)),
      8               rpad('*',400,'*')
      9          from dual
     10        connect by rownum <= 400000;
    
    已创建400000行。
    已用时间:  00: 00: 24.69
    
    SQL> insert into normal_tab
      2       select rownum,
      3               to_date( to_char(sysdate+60,'J')+TRUNC(DBMS_RANDOM.VALUE(0,60)),'J'),
      4               ceil(dbms_random.value(591,599)),
      5               ceil(dbms_random.value(18900000001,18999999999)),
      6               ceil(dbms_random.value(18900000001,18999999999)),
      7               ceil(dbms_random.value(18900000001,18999999999)),
      8               rpad('*',400,'*')
      9          from dual
     10        connect by rownum <= 400000;
    
    已创建400000行。
    
    已用时间:  00: 00: 14.69
    

      索引更新开销_普通表更新(索引是关键)

    /*
    结论:
      1. 在无索引的情况下,表的记录越大,插入的速度只会受到很小的影响,基本不会越慢。
      2. 在无索引的情况下,分区表的插入要比普通表更慢,因为插入的数据需要做判断落在哪个分区。有这方面的开销。
      3. 在有索引的情况下,表记录越大,索引越多,插入速度越慢。
      4. 在有索引的情况下,如果表非常大,索引也很多,在条件允许下,可以将索引先失效再生效,速度可以更快。
      5. 在有索引的情况下,分区表如果只是有局部索引,一般来说,分区表的插入速度比普通表更快,因为每个分区的分区索引都比较小,更新的开销自然也小。
    */
    
    -----------------------------------------------------------------------------------------------------------------------------------------------
    --表大还是小对插入的性能影响并不大
    drop table t_big purge;
    drop table t purge;
    create table t as select * from dba_objects;
    set autotrace off
    create table t_big as select * from t ;
    insert into  t_big select * from t_big;
    insert into  t_big select * from t_big;
    insert into  t_big select * from t_big;
    insert into  t_big select * from t_big;
    insert into  t_big select * from t_big;
    insert into  t_big select * from t_big;
    commit;
    
    drop table  t_small purge;
    create table t_small as select * from t where rownum<=1000;
    
    set timing on 
    insert into t_small select * from t_big;
    已创建4684096行。
    已用时间:  00: 00: 28.46
    commit;
    
    insert into t_big select * from t_big;
    已创建4684096行。
    已用时间:  00: 00: 28.22
    commit;
    
    --可以看出基本没有任何差别,虽然t_small是小表,t_big是大表。所以插入一般不会随着记录的增加越插越慢。
    什么时候会越插越慢,就是当表有索引的时候。
    因为索引需要维护,越大越多维护越困难。
    -----------------------------------------------------------------------------------------------------------------------------------------------
    drop table test1 purge;
    drop table test2 purge;
    drop table test3 purge;
    drop table t purge;
    create table t as select * from dba_objects;
    create table test1 as select * from t;
    create table test2 as select * from t;
    create table test3 as select * from t;
    create index idx_owner on test1(owner);
    create index idx_object_name on test1(object_name);
    create index idx_data_obj_id on test1(data_object_id);
    create index idx_created on test1(created);
    create index idx_last_ddl_time on test1(last_ddl_time);
    create index idx_status on test1(status);
    create index idx_t2_sta on test2(status);
    create index idx_t2_objid on test2(object_id);
    set timing on 
    --语句1(test1表有6个索引)
    insert into test1 select * from t;
    commit;
    --语句2(test2表有2个索引)
    insert into test2 select * from t;
    commit;
    --语句3(test3表有无索引)
    insert into test3 select * from t;
    commit;
    
    -------------------------------------------------------------------------------------------------------------------------------
    
    一次与出账相关的小故事
    drop table t purge;
    create table t as select * from dba_objects;
    insert into t select * from t;
    insert into t select * from t;
    commit;
    --请从这里开始注意累加的时间(从建索引到插入记录完毕)
    set timing on 
    create index idx_t_owner on t(owner);
    create index idx_t_obj_name on t(object_name);
    create index idx_t_data_obj_id on t(data_object_id);
    create index idx_t_created on t(created);
    create index idx_t_last_ddl on t(last_ddl_time);
    
    --语句1(t表有6个索引)
    insert into t select * from t;
    commit;
     
    
    --以下进行试验2
    drop table t purge;
    create table t as select * from dba_objects;
    insert into t select * from t;
    insert into t select * from t;
    commit;
    
    ---也从这里开始这里开始注意累加的时间(从插入记录完毕到建索引完毕)
    
    set timing on 
    
    --语句1(t表有6个索引,此时先不建)
    insert into t select * from t;
    
    
    create index idx_t_owner on t(owner);
    create index idx_t_obj_name on t(object_name);
    create index idx_t_data_obj_id on t(data_object_id);
    create index idx_t_created on t(created);
    create index idx_t_last_ddl on t(last_ddl_time);
    

      索引建立开销_建索引过程会产生全表锁:

    /*  
      结论:普通的对表建索引将会导致针对该表的更新操作无法进行,需要等待索引建完。更新操作将会被建索引动作阻塞。
            这种建索引的方式不会阻止针对该表的更新操作,与建普通索引相反的是,
            ONLINE建索引的动作是反过来被更新操作阻塞。
    */
    
    drop table t purge;
    create table t as select * from dba_objects;
    insert into t  select * from t;
    insert into t  select * from t;
    insert into t  select * from t;
    insert into t  select * from t;
    insert into t  select * from t;
    insert into t  select * from t;
    insert into t  select * from t;
    commit;
    select sid from v$mystat where rownum=1; 
    --12
    set timing on
    create index idx_object_id on t(object_id);
    索引已创建。
    
    已用时间:  00: 00: 15.00
    
    session 2
    sqlplus ljb/ljb
    set linesize 1000
    select sid from v$mystat where rownum=1; 
    --134
    --以下执行居然被阻塞,要直至建索引结束后,才能执行
    update t set object_id=99999 where object_id=8;
    
    
    session 3
    set linesize 1000
    select * from v$lock where sid in (12,134);
    
    SQL> select * from v$lock where sid in (134,12);
    
    ADDR     KADDR           SID TY        ID1        ID2      LMODE    REQUEST      CTIME      BLOCK
    -------- -------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------
    2EB79320 2EB7934C         12 AE        100          0          4          0        409          0
    2EB79394 2EB793C0        134 AE        100          0          4          0        254          0
    2EB79408 2EB79434         12 TO      65921          1          3          0        402          0
    2EB79574 2EB795A0         12 DL     106831          0          3          0         12          0
    2EB795E8 2EB79614         12 DL     106831          0          3          0         12          0
    0EDD7A9C 0EDD7ACC        134 TM     106831          0          0          3         10          0
    0EDD7A9C 0EDD7ACC         12 TM     106831          0          4          0         12          1
    0EDD7A9C 0EDD7ACC         12 TM         18          0          3          0         12          0
    2C0D2844 2C0D28B0         12 TS          8   25202162          6          0          4          0
    2C1A2A8C 2C1A2ACC         12 TX     393223      31633          6          0         12          0
    
    select  /*+no_merge(a) no_merge(b) */
    (select username from v$session where sid=a.sid) blocker,
    a.sid, 'is blocking',
    (select username from v$session where sid=b.sid) blockee,
    b.sid
    from v$lock a,v$lock b
    where a.block=1 and b.request>0
    and a.id1=b.id1
    and a.id2=b.id2;
    
    BLOCKER                               SID 'ISBLOCKING BLOCKEE                               SID
    ------------------------------ ---------- ----------- ------------------------------ ----------
    LJB                                    12 is blocking LJB                                   134
    

      索引建立开销_建索引过程中会全表排序:

    /*  
      结论:建索引的过程会产生排序,排序的开销一般比较大,所以要尽量避免在生产缓慢的时候建索引。
    */
    
    set linesize 266
    drop table t purge;
    create table t as select * from dba_objects;
    
    select t1.name, t1.STATISTIC#, t2.VALUE
      from v$statname t1, v$mystat t2
     where t1.STATISTIC# = t2.STATISTIC#
       and t1.name like '%sort%';
    
    NAME                                                             STATISTIC#      VALUE
    ---------------------------------------------------------------- ---------- ----------
    sorts (memory)                                                          565        462
    sorts (disk)                                                            566          0
    sorts (rows)                                                            567       2174
    
    create index idx_object_id on t(object_id);
    
    select t1.name, t1.STATISTIC#, t2.VALUE
      from v$statname t1, v$mystat t2
     where t1.STATISTIC# = t2.STATISTIC#
       and t1.name like '%sort%';
    
    NAME                                                             STATISTIC#      VALUE
    ---------------------------------------------------------------- ---------- ----------
    sorts (memory)                                                          565        463
    sorts (disk)                                                            566          0
    sorts (rows)                                                            567      75292 
    

      索引逻辑失效:

    索引逻辑失效_尽量要避免列的类型转换

    /*
      结论:什么类型就放什么值,否则会发生类型转换,导致性能问题!
    (是存放字符的字段就设varchar2类型,是存放数值的字段就设置number类型,是存放日期的字段就设置date类型)
      另外不止是类型转换,如果对列进行了运算,比如substr(列),trunc(列)等等,也会导致用不上索引,具体见案例分析中
      
    */
    --举例说明:
    drop table t_col_type purge;
    create table t_col_type(id varchar2(20),col2 varchar2(20),col3 varchar2(20));
    insert into t_col_type select rownum,'abc','efg' from dual connect by level<=10000;
    commit;
    create index idx_id on t_col_type(id);
    set linesize 1000
    set autotrace traceonly
    
    select * from t_col_type where id=6;
    
    执行计划
    --------------------------------------------------------------------------------
    | Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |            |     1 |    36 |     9   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS FULL| T_COL_TYPE |     1 |    36 |     9   (0)| 00:00:01 |
    --------------------------------------------------------------------------------
       1 - filter(TO_NUMBER("ID")=6)
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
             32  consistent gets
              0  physical reads
              0  redo size
            540  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    
    --实际上只有如下写法才可以用到索引,这个很不应该,是什么类型的取值就设置什么样的字段。
              
    select * from t_col_type where id='6';
    执行计划
    ------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |            |     1 |    36 |     2   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T_COL_TYPE |     1 |    36 |     2   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_ID     |     1 |       |     1   (0)| 00:00:01 |
    ------------------------------------------------------------------------------------------
       2 - access("ID"='6')
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              4  consistent gets
              0  physical reads
              0  redo size
            544  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed          
              
              
    

      索引物理失效_long列调整会致索引失效:

    /*
     结论:LONG修改为CLOB,居然会导致其他列的索引失效,这个问题背后产生的原因无需去纠结,有兴趣自行研究,
    */ 
    
    drop table t purge;
    create table t (object_id number,object_name long);
    create index idx_object_id on t(object_id);
    insert into t values (1,'ab');
    commit;
    
    select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
    STATUS   INDEX_NAME
    -------- ---------------
    VALID    IDX_OBJECT_ID
    
    alter table T modify object_name clob;
    set autotrace off
    
    select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
    STATUS   INDEX_NAME
    -------- --------------
    UNUSABLE IDX_OBJECT_ID
    
    
    alter index  idx_object_id rebuild;
    set autotrace off
    
    select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
    STATUS   INDEX_NAME
    -------- ---------------
    VALID    IDX_OBJECT_ID
    

      索引物理失效_move 操作会致索引失效:

    /*
     结论:很多人想用ALTER TABLE MOVE的方式来降低高水平,结果经常忽略了这个操作会导致索引失效,
     请大家通过下列的试验的回顾,以后多留意这点。
     另外alter table t shrink space; 是否能十全十美
    */ 
    
    drop table t purge;
    create table t as select * from dba_objects where object_id is not null;
    alter table t modify object_id not null;
    set autotrace off
    insert into t select * from t;
    insert into t select * from t;
    commit;
    create index idx_object_id on t(object_id);
    select index_name,status from user_indexes where index_name='IDX_OBJECT_ID';
    INDEX_NAME                     STATUS
    ------------------------------ ------
    IDX_OBJECT_ID                  VALID
    
    set linesize 1000
    set autotrace  on
    select count(*) from t;
      COUNT(*)
    ----------
      292740
    执行计划
    -------------------------------------------------------------------------------
    | Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |               |     1 |   185   (2)| 00:00:03 |
    |   1 |  SORT AGGREGATE       |               |     1 |            |          |
    |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_ID |   398K|   185   (2)| 00:00:03 |
    -------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            659  consistent gets
              0  physical reads
              0  redo size
            425  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    
    set autotrace off
    delete from t where rownum<=292000;
    commit;
    set autotrace on 
    select count(*) from t;
      COUNT(*)
    ----------
        740
    执行计划
    -------------------------------------------------------------------------------
    | Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |               |     1 |   185   (2)| 00:00:03 |
    |   1 |  SORT AGGREGATE       |               |     1 |            |          |
    |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_ID |     1 |   185   (2)| 00:00:03 |
    -------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            659  consistent gets
              0  physical reads
              0  redo size
            424  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    
    alter table t move;
    
    select count(*) from t;
    执行计划
    -------------------------------------------------------------------
    | Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |      |     1 |     6   (0)| 00:00:01 |
    |   1 |  SORT AGGREGATE    |      |     1 |            |          |
    |   2 |   TABLE ACCESS FULL| T    |   740 |     6   (0)| 00:00:01 |
    -------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
             15  consistent gets
              0  physical reads
              0  redo size
            424  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
              
    set autotrace off
    select index_name,status from user_indexes where index_name='IDX_OBJECT_ID';
    INDEX_NAME                     STATUS
    ------------------------------ --------
    IDX_OBJECT_ID                  UNUSABLE
    
    alter index idx_object_id rebuild;
    set autotrace on
    select count(*) from t;
    执行计划
    -------------------------------------------------------------------------------
    | Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |               |     1 |     3   (0)| 00:00:01 |
    |   1 |  SORT AGGREGATE       |               |     1 |            |          |
    |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_ID |   740 |     3   (0)| 00:00:01 |
    -------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              6  consistent gets
              0  physical reads
              0  redo size
            424  bytes sent via SQL*Net to client
            415  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
                        
    ----------------------------------------------------------------------------------------------------------------------------------------------
    

      索引物理失效_分区表致索引失效的操作:

    /*
    1.truncate分区会导致全局索引失效,不会导致局部索引失效。如果truncate 增加update global indexes,全局索引不会失效。
    2.drop分区会导致全局索引失效,局部索引因为drop分区,所以也不存在该分区的局部索引了。如果drop分区增加update global indexes,全局索引不会失效。
    3.split分区会导致全局索引失效,也会导致局部索引失效。如果split分区增加update global indexes,全局索引不会失效。
    4.add 分区不会导致全局索引失效,也不会导致局部索引失效。
    5.exchange会导致全局索引失效,不会导致局部索引失效。如果exchange分区增加update global indexes,全局索引不会失效。
    
    重要结论:
    1. 所有的全局索引,只要用到update global indexes ,都不会失效,其中add分区甚至不需要增加update global indexes都可以生效。
    2. 局部索引的操作都不会失效,除了split分区。切记split分区的时候,要将局部索引进行rebuild;
    
    */
    

      索引的取舍控制:

    索引取舍控制_避免表交叉重复建立索引:

    /
    *结论:一般来说,联合索引和单列索引如果有交叉,需要谨慎考虑
    比如联合索引nbr,area_code  和单列的nbr索引就有多余,因为nbr,area_code的索引可以用在单列nbr索引上。
    */
    
    --组合索引的前缀与单列索引一致
    drop table t purge;
    create table t as select * from dba_objects;
    create index idx_object_id on t(object_id,object_type);
    set autotrace traceonly
    set linesize 1000
    --以下就能用到索引,因为object_id列是前缀
    
    select * from t where object_id=19;
    执行计划
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               |     1 |   207 |     3   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T             |     1 |   207 |     3   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_OBJECT_ID |     1 |       |     2   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              4  consistent gets
              0  physical reads
              0  redo size
           1392  bytes sent via SQL*Net to client
            416  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    
    --因此这两个索引只需要保留一个就够了。
    

      索引取舍控制_删除系统从未用到的索引:

    /*  
      结论:我们可以通过alter index xxx monitoring usage的命令,对具体的索引进行监控,通过查询v$object_usage视图的USED
      字段的取值是YES还是NO,就可以知道该索引是否被使用过,具体如下:
    */
    
    ----观察查询出来的结果,删除不常使用的索引,控制索引的数量。
    
    drop table t purge;
    create table t as select * from dba_objects;
    create index idx_t_id 	  on t (object_id);
    alter index idx_t_id monitoring usage;
    
    --直接简单查询这个(因为v$object_usage只包括当前用户的索引使用记录,如果需要查出所有用户的索引使用记录,使用下面的sql)
    set linesize 166
    col INDEX_NAME for a10
    col TABLE_NAME for a10
    col MONITORING for a10
    col USED for a10
    col START_MONITORING for a25
    col END_MONITORING for a25
    select * from v$object_usage;
    
    INDEX_NAME TABLE_NAME MONITORING USED       START_MONITORING          END_MONITORING
    ---------- ---------- ---------- ---------- ------------------------- ---------------
    IDX_T_ID   T          YES        NO         12/14/2013 07:44:36
    
    --接下来继续执行一个用都索引的查询
    set autotrace traceonly
    select * from t where object_id=10;
    
    --然后再观察
    set autotrace off
    select * from v$object_usage;
    INDEX_NAME TABLE_NAME MONITORING USED       START_MONITORING          END_MONITORING
    ---------- ---------- ---------- ---------- ------------------------- ---------------
    IDX_T_ID   T          YES        YES        12/14/2013 07:44:36
    
    
    注:
    ---停止对索引的监控,观察v$object_usage状态变化
    alter index idx_t_id nomonitoring usage;
    
    INDEX_NAME TABLE_NAME MONITORING USED       START_MONITORING          END_MONITORING
    ---------- ---------- ---------- ---------- ------------------------- --------------------
    IDX_T_ID   T          NO         YES        12/14/2013 07:44:36       12/14/2013 07:46:45
    
    --如果针对当前用户下所有索引都监控,可以如下
    select 'alter index '||owner||'.'||index_name||' monitoring usage;'
    from user_indexes;
    

      索引取舍控制_组合列过多的索引很可疑

    /*
     结论:这里的例子说明了一般来说,组合索引过多效率也不见的就很高。
     除非你有特殊的手法,本例中的手法就比较特殊,
    */
    
    ---col1,col2,col3的索引情况,如果没有为COL2赋予查询条件时,COL3只能起到检验作用(依然是in的优化)
    
    drop table t purge;
    create table t as select * from dba_objects;
    UPDATE t SET OBJECT_ID=20 WHERE ROWNUM<=26000;
    UPDATE t SET OBJECT_ID=21 WHERE OBJECT_ID<>20;
    Update t set object_id=22 where rownum<=10000;
    COMMIT;
    
    create index idx_union on t(object_type,object_id,owner);
    set autotrace off
    alter session set statistics_level=all ;
    set linesize 1000
    select * from t where object_type='VIEW' and OWNER='LJB';
    select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
    ---------------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
    ---------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |           |      1 |        |      4 |00:00:00.01 |      24 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T         |      1 |      9 |      4 |00:00:00.01 |      24 |
    |*  2 |   INDEX RANGE SCAN          | IDX_UNION |      1 |     22 |      4 |00:00:00.01 |      21 |
    ---------------------------------------------------------------------------------------------------
    
    select /*+INDEX(T,idx_union)*/ * from t T where object_type='VIEW' and OBJECT_ID IN (20,21,22) AND OWNER='LJB';
    ----------------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
    ----------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |           |      1 |        |      4 |00:00:00.01 |      13 |
    |   1 |  INLIST ITERATOR             |           |      1 |        |      4 |00:00:00.01 |      13 |
    |   2 |   TABLE ACCESS BY INDEX ROWID| T         |      3 |      1 |      4 |00:00:00.01 |      13 |
    |*  3 |    INDEX RANGE SCAN          | IDX_UNION |      3 |      1 |      4 |00:00:00.01 |      10 |
    ----------------------------------------------------------------------------------------------------
    
    类似
    select /*+INDEX(T,idx_union)*/ * from t T where (object_type='VIEW' and OBJECT_ID =20 AND OWNER='LJB')
                                                    or (object_type='VIEW' and OBJECT_ID =21 AND OWNER='LJB')
                                                    or (object_type='VIEW' and OBJECT_ID =22 AND OWNER='LJB')
                                                    
                                                   
    ---一般来说,组合索引中,第2列脱离第1列无意义,第3列脱离第2列无意义,第4列脱离第3列无意义。所以如果像这个案例中,假如无法有效
    的增加object_id条件进去,这个object_type,object_id,owner的三列组合索引,还不如直接建成object_type单列索引
    

      

  • 相关阅读:
    Android5.0全透明状态栏效果
    从关系库导入数据到hive-hbase表中
    Find Minimum in Rotated Sorted Array II 旋转数组中找最小值(有反复元素) @LeetCode
    深度解析国内首个云原生数据库POLARDB的“王者荣耀”
    深度解析国内首个云原生数据库POLARDB的“王者荣耀”
    nodejs中httpserver的安装和使用
    Java的循环语句
    Java的循环语句
    Java的循环语句
    Mybatis-generator生成Service和Controller
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/12330828.html
Copyright © 2011-2022 走看看