zoukankan      html  css  js  c++  java
  • Oracle学习笔记从案例中看索引的开销(十四)

    索引访问开销_设置索引并行属性引风波

    /*
      为了提高建索引的效率,采用了并行的方式,并且设到了索引的属性中去了,引发了性能故障。
      一般来说,如果我们要做并行的操作,建议用HINT的方式给查询语句加索引,比如/*+parallel n*/
    */
    
    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;
    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;
    
    set timing on
    create index idx_object_id on t(object_id) parallel 8;
    
    索引已创建。
    
    已用时间:  00: 00: 09.85
    
    
    select index_name,degree from user_indexes where table_name='T';
    INDEX_NAME                     DEGREE
    ------------------------------ -------
    IDX_OBJECT_ID                  8
    
    
    
    set linesize 1000
    set autotrace traceonly
    
    select count(*) from t;
    执行计划
    ----------------------------------------------------------------------------------------------------------------
    | Id  | Operation                 | Name          | Rows  | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
    ----------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT          |               |     1 |  5797   (2)| 00:01:10 |        |      |         |
    |   1 |  SORT AGGREGATE           |               |     1 |            |          |        |      |         |
    |   2 |   PX COORDINATOR          |               |       |            |          |        |      |         |
    |   3 |    PX SEND QC (RANDOM)    | :TQ10000      |     1 |            |          |  Q1,00 | P->S | QC (RAND)  |
    |   4 |     SORT AGGREGATE        |               |     1 |            |          |  Q1,00 | PCWP |         |
    |   5 |      PX BLOCK ITERATOR    |               |  8100K|  5797   (2)| 00:01:10 |  Q1,00 | PCWC |         |
    |   6 |       INDEX FAST FULL SCAN| IDX_OBJECT_ID |  8100K|  5797   (2)| 00:01:10 |  Q1,00 | PCWP |         |
    ----------------------------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
             24  recursive calls
              0  db block gets
          25365  consistent gets
          20769  physical reads
              0  redo size
            426  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
    alter index   IDX_OBJECT_ID noparallel;
    select index_name,degree from user_indexes where table_name='T';
    INDEX_NAME                     DEGREE
    ------------------------------ -------
    IDX_OBJECT_ID                  1        
    
    SQL> select count(*) from t;
    执行计划
    -------------------------------------------------------------------------------
    | Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT      |               |     1 |  5797   (2)| 00:01:10 |
    |   1 |  SORT AGGREGATE       |               |     1 |            |          |
    |   2 |   INDEX FAST FULL SCAN| IDX_OBJECT_ID |  8100K|  5797   (2)| 00:01:10 |
    -------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          20828  consistent gets
              0  physical reads
              0  redo size
            426  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
    

      索引更新开销_分区表与插入性能的提升

    /*  
      结论:如果表没有索引,插入的速度一般都不会慢,只有在有索引的情况下,才要考虑插入速度的优化。
            如果表有大量索引,一般来说,分区表的局部索引由于只需要更新局部分区的索引,所以索引的开销会比较小,所以插入性能比
            有着相同的记录数,列及索引的普通表更快。
      
    */
    
    --构造分区表,插入数据。
    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: 51.20
    
    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: 01: 20.04
    

      索引建立开销_未用online建索引酿大错

    /*  
      结论:普通的对表建索引将会导致针对该表的更新操作无法进行,需要等待索引建完。更新操作将会被建索引动作阻塞。
            而ONLINE建索引的方式却是不会阻止针对该表的更新操作,与建普通索引相反的是,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) online;
    索引已创建。
    
    
    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);
    
    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        278          0
    2EB79394 2EB793C0        134 AE        100          0          4          0        303          0
    2EB79408 2EB79434         12 DL     106831          0          3          0         25          0
    2EB79574 2EB795A0         12 DL     106831          0          3          0         25          0
    2EB795E8 2EB79614         12 OD     106831          0          4          0         25          0
    2EB7965C 2EB79688         12 TX     131079      31688          0          4         11          0
    0EDD7A9C 0EDD7ACC        134 TM     106831          0          3          0         23          0
    0EDD7A9C 0EDD7ACC         12 TM     106831          0          2          0         25          0
    0EDD7A9C 0EDD7ACC         12 TM     106834          0          4          0         25          0
    2C17C3B8 2C17C3F8        134 TX     131079      31688          6          0         23          1
    2C1A2448 2C1A2488         12 TX     589853      31754          6          0         25          0
    
    已选择11行。
    
    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                                   134 is blocking LJB                                    12
    

      索引去哪儿_like与 %之间

    /*  
      结论:索引遇到like '%LJB' 或者是'%LJB%'的查询,是用不到索引的(除非是全索引访问,这是索引能回答问题的一个例外)。
      不过like 'LJB%'是可以用到索引的。原理其实很简单,从索引有序性就可以推理到原因了。
      让'%LJB'用的索引的另类方法,这里涉及到了函数索引的知识,
      另外即便是'%LJB%',也不见的就一定用不到索引,可以考虑全文检索,
      
    */
    
    思路:
    1. 全文检索
    2. 寻找函数构造的机会,并建函数索引
    
    drop table t purge;
    create table t as select * from dba_objects where object_id is not null;
    set autotrace off
    update t set object_id=rownum;
    update t set object_name='AAALJB' where object_id=8;
    update t set object_name='LJBAAA' where object_id=10;
    commit;
    create index idx_object_name on t(object_name);
    
    SET AUTOTRACE ON
    SET LINESIZE 1000
    
    select object_name,object_id from t where object_name like 'LJB%';
    
    OBJECT_NAME             OBJECT_ID
    ------------------------------ ---
    LJBAAA                         10
    LJB_TMP_SESSION             72521
    LJB_TMP_SESSION             72910
    LJB_TMP_TRANSACTION         72522
    LJB_TMP_TRANSACTION         72911
    
    已选择5行。
    
    执行计划
    -----------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |                 |     5 |   395 |     6   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T               |     5 |   395 |     6   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_OBJECT_NAME |     5 |       |     3   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              9  consistent gets
              0  physical reads
              0  redo size
            602  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)
              5  rows processed
              
              
    SQL> select object_name,object_id from t where object_name like '%LJB%';
    
    OBJECT_NAME                 OBJECT_ID
    ---------------------------------- ---
    AAALJB                              8
    LJBAAA                             10
    LJB_TMP_SESSION                 72521
    LJB_TMP_TRANSACTION             72522
    LJB_TMP_SESSION                 72910
    LJB_TMP_TRANSACTION             72911
    
    已选择6行。
    
    执行计划
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |    12 |   948 |   292   (1)| 00:00:04 |
    |*  1 |  TABLE ACCESS FULL| T    |    12 |   948 |   292   (1)| 00:00:04 |
    --------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
           1049  consistent gets
              0  physical reads
              0  redo size
            653  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)
              6  rows processed
              
              
              
    select object_name,object_id from t where object_name like '%LJB';  
    OBJECT_NAME           OBJECT_ID
    ---------------------------- --
    AAALJB                        8      
    
    执行计划
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |    12 |   948 |   292   (1)| 00:00:04 |
    |*  1 |  TABLE ACCESS FULL| T    |    12 |   948 |   292   (1)| 00:00:04 |
    --------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
           1049  consistent gets
              0  physical reads
              0  redo size
            496  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  reverse('%LJB') from dual;
    REVER
    -----
    BJL%          
    
    create index idx_reverse_objname on t(reverse(object_name));
    set autotrace on 
    select object_name,object_id from t where reverse(object_name) like reverse('%LJB'); 
    
    OBJECT_NAME           OBJECT_ID
    ---------------------------- --
    AAALJB                        8
    
    执行计划
    ---------------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |                     |  3596 |   509K|   290   (0)| 00:00:04 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T                   |  3596 |   509K|   290   (0)| 00:00:04 |
    |*  2 |   INDEX RANGE SCAN          | IDX_REVERSE_OBJNAME |   647 |       |     6   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              5  consistent gets
              0  physical reads
              0  redo size
            496  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
    

      move 致索引失效:

    /*
      结论:
      这次案例,是涉及有主外键的两表关联查询的性能,索引失效导致NL连接性能下降。
    */
    
    drop table t_p cascade constraints purge;
    drop table t_c cascade constraints purge;
    
    CREATE TABLE T_P (ID NUMBER, NAME VARCHAR2(30));
    ALTER TABLE T_P ADD CONSTRAINT  T_P_ID_PK  PRIMARY KEY (ID);
    CREATE TABLE T_C (ID NUMBER, FID NUMBER, NAME VARCHAR2(30));
    
    ALTER TABLE T_C ADD CONSTRAINT FK_T_C FOREIGN KEY (FID) REFERENCES T_P (ID);
    
    INSERT INTO T_P SELECT ROWNUM, TABLE_NAME FROM ALL_TABLES;
    INSERT INTO T_C SELECT ROWNUM, MOD(ROWNUM, 1000) + 1, OBJECT_NAME  FROM ALL_OBJECTS;
    COMMIT;
    
    CREATE INDEX IND_T_C_FID ON T_C (FID);
    
    SELECT TABLE_NAME,INDEX_NAME,STATUS FROM USER_INDEXES WHERE INDEX_NAME='IND_T_C_FID';
    TABLE_NAME                     INDEX_NAME                     STATUS
    ------------------------------ ------------------------------ -------
    T_C                            IND_T_C_FID                    VALID
    
    --不小心失效了,比如操作了
    ALTER TABLE T_C MOVE;
    
    SELECT TABLE_NAME,INDEX_NAME,STATUS FROM USER_INDEXES WHERE INDEX_NAME='IND_T_C_FID';
    TABLE_NAME                     INDEX_NAME                     STATUS
    ------------------------------ ------------------------------ --------
    T_C                            IND_T_C_FID                    UNUSABLE
    
    
    --结果查询性能是这样的:
    SET LINESIZE 1000
    SET AUTOTRACE TRACEONLY
    SELECT A.ID, A.NAME, B.NAME FROM T_P A, T_C B WHERE A.ID = B.FID AND A.ID = 880;
    执行计划
    ------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |           |    25 |  1500 |   111   (1)| 00:00:02 |
    |   1 |  NESTED LOOPS                |           |    25 |  1500 |   111   (1)| 00:00:02 |
    |   2 |   TABLE ACCESS BY INDEX ROWID| T_P       |     1 |    30 |     0   (0)| 00:00:01 |
    |*  3 |    INDEX UNIQUE SCAN         | T_P_ID_PK |     1 |       |     0   (0)| 00:00:01 |
    |*  4 |   TABLE ACCESS FULL          | T_C       |    25 |   750 |   111   (1)| 00:00:02 |
    ------------------------------------------------------------------------------------------
       3 - access("A"."ID"=880)
       4 - filter("B"."FID"=880)
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            394  consistent gets
              0  physical reads
              0  redo size
           3602  bytes sent via SQL*Net to client
            459  bytes received via SQL*Net from client
              6  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
             72  rows processed
             
             
    ---将失效索引重建后
    ALTER INDEX IND_T_C_FID   REBUILD;
    查询性能是这样的:
    SELECT A.ID, A.NAME, B.NAME FROM T_P A, T_C B WHERE A.ID = B.FID AND A.ID = 880;
    执行计划
    --------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |             |    72 |  4320 |    87   (0)| 00:00:02 |
    |   1 |  NESTED LOOPS                |             |    72 |  4320 |    87   (0)| 00:00:02 |
    |   2 |   TABLE ACCESS BY INDEX ROWID| T_P         |     1 |    30 |     0   (0)| 00:00:01 |
    |*  3 |    INDEX UNIQUE SCAN         | T_P_ID_PK   |     1 |       |     0   (0)| 00:00:01 |
    |   4 |   TABLE ACCESS BY INDEX ROWID| T_C         |    72 |  2160 |    87   (0)| 00:00:02 |
    |*  5 |    INDEX RANGE SCAN          | IND_T_C_FID |    72 |       |     1   (0)| 00:00:01 |
    --------------------------------------------------------------------------------------------
       3 - access("A"."ID"=880)
       5 - access("B"."FID"=880)
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
             81  consistent gets
              0  physical reads
              0  redo size
           3602  bytes sent via SQL*Net to client
            459  bytes received via SQL*Net from client
              6  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
             72  rows processed      
    

      move 致索引失效引锁等待:

    /*  
      结论:由于move 外键所在的表,导致外键的表的索引失效,导致主外键的表更新起来举步维艰,频频被锁,如下: 
    */
    
    --外键索引性能研究之准备
    drop table t_p cascade constraints purge;
    drop table t_c cascade constraints purge;
    
    CREATE TABLE T_P (ID NUMBER, NAME VARCHAR2(30));
    ALTER TABLE T_P ADD CONSTRAINT  T_P_ID_PK  PRIMARY KEY (ID);
    CREATE TABLE T_C (ID NUMBER, FID NUMBER, NAME VARCHAR2(30));
    
    ALTER TABLE T_C ADD CONSTRAINT FK_T_C FOREIGN KEY (FID) REFERENCES T_P (ID);
    
    INSERT INTO T_P SELECT ROWNUM, TABLE_NAME FROM ALL_TABLES;
    INSERT INTO T_C SELECT ROWNUM, MOD(ROWNUM, 1000) + 1, OBJECT_NAME  FROM ALL_OBJECTS;
    COMMIT;
    
    create index idx_IND_T_C_FID on T_C(FID);
    
    --以下操作导致外键索引失效
    ALTER TABLE T_C MOVE;
    
    外键索引删除后,立即有锁相关问题
    --首先开启会话1
    select sid from v$mystat where rownum=1;
    DELETE T_C WHERE ID = 2;
    --接下来开启会话2,也就是开启一个新的连接
    select sid from v$mystat where rownum=1;
     
    --然后执行如下进行观察
    DELETE T_P WHERE ID = 2000;
    --居然发现卡住半天不动了!
     
    
    --假如外键有索引,就不会产生死锁情况,
    --首先开启会话1
    ALTER INDEX idx_IND_T_C_FID REBUIDL;
    select sid from v$mystat where rownum=1;
    DELETE T_C WHERE FID = 2;
    
    --接下来开启会话2,也就是开启一个新的连接
    select sid from v$mystat where rownum=1;
    DELETE T_P WHERE ID = 2000;
    

      shrink索引不失效也被弃用

    /*  
      结论:alter table t shrink的方式降低表的高水平位,也不会导致索引失效,却无法消除索引的大量空块。
      最终导致虽然索引不失效,查询依然不用索引,具体见案例如下:  
    */
    
    --这里用alter table t shrink的方式降低高水平位,结果避免了索引的失效,不过索引不失效了,是否索引就一定会被用到吗,
    
    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);         
    set linesize 1000
    set autotrace  on
    select count(*) from t;
    set autotrace off
    delete from t where rownum<=292000;
    commit;
    set autotrace on 
    select count(*) from t;
    alter table t enable row movement;
    alter table t shrink space;
    select count(*) from t;
    执行计划
    ----------------------------------------------------------
    Plan hash value: 2966233522
    
    -------------------------------------------------------------------
    | Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |      |     1 |     5   (0)| 00:00:01 |
    |   1 |  SORT AGGREGATE    |      |     1 |            |          |
    |   2 |   TABLE ACCESS FULL| T    |   740 |     5   (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                  VALID
    
    set autotrace on 
    --原来发现走了,还更慢。
    select /*+index(t)*/ count(*) from t;
    执行计划
    --------------------------------------------------------------------------
    | Id  | Operation        | Name          | Rows  | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT |               |     1 |   675   (1)| 00:00:09 |
    |   1 |  SORT AGGREGATE  |               |     1 |            |          |
    |   2 |   INDEX FULL SCAN| IDX_OBJECT_ID |   740 |   675   (1)| 00:00:09 |
    --------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
            649  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 shrink 不会导致索引失效,但是索引块的高水平无法释放。还是会产生大量的逻辑读。          
    

      范围查询为何就用不到索引:

    /*  
      结论:方向键索引可以消除索引热块访问竞争,是一个很不错的技术,该技术只能用在等值查询,而不能用在范围查询。
      以下是生产中的一个案例,有人讲索引建成了反向键索引,却忘记了该系统有大量范围查询,还在纠结为何范围查询用不到索引。 
    */
    
    --这里说的是反向键索引的故事
    
    drop table t purge;
    create table t (id number,deal_date date,area_code number,nbr number,contents varchar2(4000));
    set autotrace off
    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) reverse;
    set linesize 1000
    set autotrace off
    select index_name,index_type from user_indexes where table_name='T';
    INDEX_NAME                     INDEX_TYPE
    ------------------------------ -----------
    IDX_T_ID                       NORMAL/REV
    
    
    set autotrace traceonly 
    --以下语句缘何用不到索引。
    select * from t where id=28;
    执行计划
    ----------------------------------------------------------------------------------------
    | Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |          |    69 |   138K|   401   (0)| 00:00:05 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T        |    69 |   138K|   401   (0)| 00:00:05 |
    |*  2 |   INDEX RANGE SCAN          | IDX_T_ID |   486 |       |     1   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    --不过奇怪的是,缘何下列语句却用不到索引,索引去哪儿?
    
    select * from t where id>=28 and id<=50;
    执行计划
    ----------------------------------------------------------
    Plan hash value: 1601196873
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |   304 |   608K|  1709   (1)| 00:00:21 |
    |*  1 |  TABLE ACCESS FULL| T    |   304 |   608K|  1709   (1)| 00:00:21 |
    --------------------------------------------------------------------------
    统计信息
    ------------------------------------------------------
              0  recursive calls
              0  db block gets
           6303  consistent gets
              0  physical reads
              0  redo size
           2263  bytes sent via SQL*Net to client
            426  bytes received via SQL*Net from client
              3  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
             23  rows processed
    
    --原因在于这个索引不是普通索引,是为了避免热块竞争而建立的反向键索引,根本部支持范围查询,只支持等值查询。
    

      回收站还原表后的苦难经历

    /*  
      结论:关于误drop表后从回收站中取回表,需要记住一些细节,比如,这时其实该表的索引已经丢了。
      一定要在恢复该表的同时记住将索引及约束等属性补完善。
    */
    
    drop table t purge;
    create table t as select * from dba_objects;
    create index idx_object_id on t(object_id);
    set autotrace off
    select index_name,status from user_indexes where table_name='T';
    
    INDEX_NAME                     STATUS
    ------------------------------ --------
    IDX_OBJECT_ID                  VALID
    
    drop table t;
    
    flashback table t to before drop;
    
    --取回来了,其实索引丢了
    select status from user_indexes where index_name='IDX_OBJECT_ID';
    未选定行
    
    --后来系统运行的很慢,很慢.....
    

      回收站恢复与约束的那点事:

    /*  
      结论:关于误drop表,然后从回收站中取回表后,除了索引会丢,约束一样也会丢失。 
    */
    
    drop table t_p cascade constraints purge;
    drop table t_c cascade constraints purge;
    
    CREATE TABLE T_P (ID NUMBER, NAME VARCHAR2(30));
    ALTER TABLE T_P ADD CONSTRAINT  T_P_ID_PK  PRIMARY KEY (ID);
    CREATE TABLE T_C (ID NUMBER, FID NUMBER, NAME VARCHAR2(30));
    
    ALTER TABLE T_C ADD CONSTRAINT FK_T_C FOREIGN KEY (FID) REFERENCES T_P (ID);
    set autotrace off
    INSERT INTO T_P SELECT ROWNUM, TABLE_NAME FROM ALL_TABLES;
    INSERT INTO T_C SELECT ROWNUM, MOD(ROWNUM, 1000) + 1, OBJECT_NAME  FROM ALL_OBJECTS;
    COMMIT;
    
    CREATE INDEX IND_T_C_FID ON T_C (FID);
    
    
    --以下删除数据会失败
    delete from  t_p where id=8;
    第 1 行出现错误:
    ORA-02292: 违反完整约束条件 (LJB.FK_T_C) - 已找到子记录
    
    ---换一个顺序可以(先删除t_c的记录,再删除t_p)
    delete from t_c where fid=8; 
    delete from t_p where id=8; 
    commit;
    --当然,也可以采用约束下失效再生效的方法
    
    
    --以下删除操作会失败
    drop table t_p;
    ORA-02449: unique/primary keys in table referenced by foreign keys
    ---不过强制可以删除成功(drop table t_p cascade constraint;)
    
    --换一个顺序(先删t_c,再删t_p就可以了)
    SQL> drop table t_c;
    表已删除。
    SQL> drop table t_p;
    表已删除。
    --当然,也可以采用约束下失效再生效的方法
    
    ----注意,现实中的一个案例,外键所在的表被drop了,从回收站取回来的时候,记得,不仅是索引没了,约束也丢了。
    
    DROP TABLE T_C ;
    FLASHBACK TABLE T_C TO BEFORE  DROP;
    
    ---发现不止是外键的索引丢失了,约束也丢失了。
    SELECT TABLE_NAME,
           CONSTRAINT_NAME,
           STATUS,
           CONSTRAINT_TYPE,
           R_CONSTRAINT_NAME
      FROM USER_CONSTRAINTS
     WHERE TABLE_NAME = 'T_C';
     
    未选定行
    
    prompt <p>失效对象
    select t.object_type,
           t.object_name,
           'alter ' ||decode(object_type, 'PACKAGE BODY', 'PACKAGE', 'TYPE BODY','TYPE',object_type) || ' ' ||owner || '.' || object_name || ' ' ||decode(object_type, 'PACKAGE BODY', 'compile body', 'compile') || ';'
      from user_objects t
     where  STATUS='INVALID'
     order by 1, 2;
    

      最典型的时间查询通病:

    /*  
      结论:避免对列进行运算,否则将用不到索引,除非使用函数索引。
       where trunc(created)>=TO_DATE('2013-12-14', 'YYYY-MM-DD')
       and trunc(created)<=TO_DATE('2013-12-15', 'YYYY-MM-DD')
    */
    
    drop table t purge;
    create table t as select * from dba_objects;
    create index idx_object_id on t(created);
    set autotrace traceonly
    set linesize 1000
    
    --以下写法大量的出现在开发人员的代码中,是一个非常常见的通病,由于对列进行了运算,所以用不到索引,如下:
    select * from t where trunc(created)>=TO_DATE('2013-12-14', 'YYYY-MM-DD')
    and trunc(created)<=TO_DATE('2013-12-15', 'YYYY-MM-DD');
    
    执行计划
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |    12 |  2484 |   296   (2)| 00:00:04 |
    |*  1 |  TABLE ACCESS FULL| T    |    12 |  2484 |   296   (2)| 00:00:04 |
    --------------------------------------------------------------------------
       1 - filter(TRUNC(INTERNAL_FUNCTION("CREATED"))>=TO_DATE(' 2013-12-14
                  00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
                  TRUNC(INTERNAL_FUNCTION("CREATED"))<=TO_DATE(' 2013-12-15 00:00:00',
                  'syyyy-mm-dd hh24:mi:ss'))
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
           1049  consistent gets
              0  physical reads
              0  redo size
           1390  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 where created>=TO_DATE('2013-12-14', 'YYYY-MM-DD')
    and created<TO_DATE('2013-12-15', 'YYYY-MM-DD')+1;
    
    执行计划
    ---------------------------------------------------------------------------------------------
    | 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 |
    ---------------------------------------------------------------------------------------------
       2 - access("CREATED">=TO_DATE(' 2013-12-14 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
                  AND "CREATED"<TO_DATE(' 2013-12-16 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              3  consistent gets
              0  physical reads
              0  redo size
           1393  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
    

      请注意这写法是案例:

    /*  
      结论:避免对列进行运算,否则将用不到索引,除非使用函数索引。
      之间已经看过了一个时间的例子
       where trunc(created)>=TO_DATE('2013-12-14', 'YYYY-MM-DD')
       and trunc(created)<=TO_DATE('2013-12-15', 'YYYY-MM-DD') 导致用不到索引,
      
    */
    drop table t purge;
    create table t as select * from dba_objects;
    create index idx_object_id on t(object_id);
    VARIABLE id NUMBER;
    EXECUTE :id := 8;
    set linesize 1000
    set autotrace traceonly
    select * from t where object_id/2=:id;
    
    执行计划
    --------------------------------------------------------------------------------
    | 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 where object_id=:id*2;
    执行计划
    ---------------------------------------------------------------------------------------------
    | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |               |   685 |   138K|     6   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T             |   685 |   138K|     6   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | IDX_OBJECT_ID |   274 |       |     1   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              4  consistent gets
              0  physical reads
              0  redo size
           1407  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  
    

      组合升降序排序索引有玄机:

    /*  
      结论:索引能够消除排序,但是如果排序是部分升序部分降序,就必须建对应部分升降序的索引,否则无法用这个来消除排序。
            比如order by col1 desc col2 asc,我们可以建(col1 desc,col2 asc)的索引。
            值得一提的是,如果你的语句变成 order by col1 asc col2 desc,
            之前的(col1 desc,col2 asc)的索引依然可以起到避免排序的作用DESCING。
    */
    
    drop table t purge;
    create table t as select * from dba_objects where object_id is not null ;
    set autotrace off
    insert into t select * from t;
    insert into t select * from t;
    commit;
    create index idx_t on t (owner,object_id);
    alter table t modify owner not null;
    alter table t modify object_id  not null;
    
    set linesize 1000
    set autotrace traceonly
    
    --听说order by 列有索引可以消除排序,测试发现,Oracle选择不用索引,排序依然存在,索引去哪儿?
    select  * from t a order by owner desc ,object_type asc;
    执行计划
    -----------------------------------------------------------------------------------
    | Id  | Operation          | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |      |   398K|    78M|       | 19133   (1)| 00:03:50 |
    |   1 |  SORT ORDER BY     |      |   398K|    78M|    94M| 19133   (1)| 00:03:50 |
    |   2 |   TABLE ACCESS FULL| T    |   398K|    78M|       |  1177   (1)| 00:00:15 |
    -----------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
           4209  consistent gets
              0  physical reads
              0  redo size
       13981752  bytes sent via SQL*Net to client
         215080  bytes received via SQL*Net from client
          19517  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
         292740  rows processed
    
          
    --换个思路,建如下索引      
    drop index idx_t;
    create index idx_t on t(owner desc,object_type asc);
    
    --哦,索引再这,效率果然提高了,COST比未用索引导致排序的代价19133低,是14687。
    select  * from t a order by owner desc ,object_type asc;
    执行计划
    -------------------------------------------------------------------------------------
    | Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |       |   398K|    78M| 14687   (1)| 00:02:57 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T     |   398K|    78M| 14687   (1)| 00:02:57 |
    |   2 |   INDEX FULL SCAN           | IDX_T |   398K|       |  1085   (1)| 00:00:14 |
    -------------------------------------------------------------------------------------
    统计信息
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          52710  consistent gets
              0  physical reads
              0  redo size
       13821025  bytes sent via SQL*Net to client
         215080  bytes received via SQL*Net from client
          19517  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
         292740  rows processed
    

      早知道有虚拟索引就好了

    /*  
      结论:在数据库优化中,索引的重要性不言而喻。但是,在性能调整过程中,一个索引是否能被查询用到,在索
    引创建之前是无法确定的,而创建索引是一个代价比较高的操作,尤其是数据量较大的时候。这时你就应该考虑使用虚拟索引来做个试验
    */
    
    drop table t purge;
    create table t as select * from dba_objects;
    --创建虚拟索引,首先要将_use_nosegment_indexes的隐含参数设置为true
    alter session set "_use_nosegment_indexes"=true;
    --虚拟索引的创建语法比较简单,实际上就是普通索引语法后面加一个nosegment关键字
    create index ix_t_id on t(object_id) nosegment;
    explain plan for select * from t where object_id=1;
    set linesize 1000
    select * from table(dbms_xplan.display());
    set autotrace traceonly
    select * from t where object_id=1;
    set autotrace off
    --以下看的是真实执行计划,显然是用不到索引。
    alter session set statistics_level=all;
    select * from t where object_id=1;
    select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
    --从数据字段中是无法找到这个索引的。
    select index_name,status from user_indexes where table_name='T';
    
    注:虚拟索引的几个特点
    1. 无法执行alter index
    2. 不能创建和虚拟索引同名的实际索引
    3. 可以创建和虚拟索引包含相同列但不同名的实际索引
    4. 在10g使用回收站特性的时候,虚拟索引必须显式drop,或者在drop table后purge table后,才能创建同名的索引
    5. 虚拟索引分析并且有效,但是数据字典里查不到结果,估计是oracle内部临时保存了分析结果
    

      

  • 相关阅读:
    对象的绑定方法
    属性查找
    定制对象独有特征
    类和对象
    面向对象编程介绍
    面向对象程序设计的由来(历史故事)
    基于socketserver实现并发的socket套接字编程
    基于UDP协议的socket套接字编程
    解决粘包问题
    copy 合并
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/12360084.html
Copyright © 2011-2022 走看看