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单列索引

  

posted @ 2020-02-19 13:00  石shi  阅读(454)  评论(0编辑  收藏  举报