当前位置:主页 > 查看内容

Oracle中关于外键缺少索引的探讨和总结

发布时间:2021-04-14 00:00| 位朋友查看

简介:本文转载自微信公众号「DBA闲思杂想录」,作者潇湘隐者 。转载本文请联系DBA闲思杂想录公众号。 在ORACLE数据库中,定义外键约束时,ORACLE是不会自动创建对应索引的,必须手动在外键约束相关的列上创建索引。那么外键字段上是否有必要创建索引呢?如果有必要……

本文转载自微信公众号「DBA闲思杂想录」,作者潇湘隐者 。转载本文请联系DBA闲思杂想录公众号

在ORACLE数据库中,定义外键约束时,ORACLE是不会自动创建对应索引的,必须手动在外键约束相关的列上创建索引。那么外键字段上是否有必要创建索引呢?如果有必要的话,巡检时,如何找出外键字段上没有创建索引的相关表,并生成对应的索引的脚本呢?

外键缺失索引的影响

外键列上缺少索引会带来三个问题,限制并发性、影响性能、还有可能造成死锁。所以对于绝大部分场景,我们应该尽量考虑在外键上面创建索引

  1. 影响性能。如果子表外键没有创建索引,那么当父表查询关联子表时,子表将进行全表扫描。影响表连接方式。
  2. 影响并发。无论是更新父表主键,或者删除一个父记录,都会在子表中加一个表锁(在这条语句完成前,不允许对子表做任何修改)。这就会不必要地锁定更多的行,而影响并发性
  3. 在特殊情况下,还有可能造成死锁。

我们先来看看一个简单的例子,看看当外键缺失索引时,子表是否进行全表扫描,如下所示,表EMP与DEPT存在主外键关系:

  1. SQL> set autotrace on
  2. SQL>  
  3. SQL> SELECT  D.DEPTNO, COUNT(*) 
  4.   2  FROM SCOTT.EMP E INNER JOIN SCOTT.DEPT D ON E.DEPTNO =D.DEPTNO 
  5.   3  GROUP BY D.DEPTNO; 
  6.   
  7.     DEPTNO   COUNT(*) 
  8. ---------- ---------- 
  9.         30          6 
  10.         20          5 
  11.         10          3 
  12.   
  13.   
  14. Execution Plan 
  15. ---------------------------------------------------------- 
  16. Plan hash value: 4067220884 
  17.   
  18. --------------------------------------------------------------------------- 
  19. | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
  20. --------------------------------------------------------------------------- 
  21. |   0 | SELECT STATEMENT   |      |     3 |     9 |     4  (25)| 00:00:01 | 
  22. |   1 |  HASH GROUP BY     |      |     3 |     9 |     4  (25)| 00:00:01 | 
  23. |*  2 |   TABLE ACCESS FULL| EMP  |    14 |    42 |     3   (0)| 00:00:01 | 
  24. --------------------------------------------------------------------------- 
  25. Predicate Information (identified by operation id): 
  26. --------------------------------------------------- 
  27.    2 - filter("E"."DEPTNO" IS NOT NULL
  28.   
  29.   
  30. Statistics 
  31. ---------------------------------------------------------- 
  32.           1  recursive calls 
  33.           0  db block gets 
  34.           7  consistent gets 
  35.           0  physical reads 
  36.           0  redo size 
  37.         665  bytes sent via SQL*Net to client 
  38.         524  bytes received via SQL*Net from client 
  39.           2  SQL*Net roundtrips to/from client 
  40.           0  sorts (memory) 
  41.           0  sorts (disk) 
  42.           3  rows processed 

如上所示,当外键字段没有索引时,父表与子表关联时,子表会进行全表扫描,下面,我在外键字段创建索引后,就能避免子表表扫描了。

  1. CREATE INDEX SCOTT.IX_DEPTNO ON SCOTT.EMP ("DEPTNO") TABLESPACE USERS; 

当然这两个表的数据量实在是太少了,性能上差别不大,当数据量增长上去后,这个性能差异就会比较明显了。如下例子所示,我们构造一个数据量相对较大的父表与子表的案例:

  1. create table parent_tb_test  
  2.     id         number(10), 
  3.     name       varchar2(32), 
  4.     constraint pk_parent_tb_test primary key(id) 
  5. ); 
  6.   
  7. create table child_tb_test 
  8.      c_id        number(10), 
  9.      f_id        number(10), 
  10.      child_name  varchar2(32), 
  11.      constraint  pk_child_tb_test primary key(c_id), 
  12.      foreign     key(f_id) references parent_tb_test 
  13. ); 
  14.   
  15.   
  16. begin 
  17.      
  18. for index_num in 1 .. 10000 loop 
  19.     insert into parent_tb_test 
  20.     select index_num , 'kerry' || to_char(index_num) from dual; 
  21.      
  22.     if mod(index_num,100) = 0 then 
  23.         commit
  24.     end if; 
  25. end loop; 
  26.   
  27.      commit
  28.      
  29. end
  30.   
  31. declare index_num number :=1; 
  32. begin 
  33.   
  34.     for index_parent  in 1 .. 10000 loop 
  35.         for index_child in 1 .. 1000 loop 
  36.              insert into child_tb_test 
  37.              select index_num, index_parent, 'child' || to_char(index_child) from dual; 
  38.               
  39.              index_num := index_num +1; 
  40.              if mod(index_child,1000) = 0 then 
  41.                  commit
  42.              end if; 
  43.         end loop; 
  44.     end loop; 
  45.   
  46.     commit
  47. end
  48.   
  49.   
  50. SQL> execute dbms_stats.gather_table_stats(ownname => 'TEST', tabname =>'PARENT_TB_TEST', estimate_percent =>DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL COLUMNS SIZE AUTO'); 
  51.   
  52. PL/SQL procedure successfully completed. 
  53.   
  54. SQL> execute dbms_stats.gather_table_stats(ownname => 'TEST', tabname =>'CHILD_TB_TEST', estimate_percent =>DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL COLUMNS SIZE AUTO'); 
  55.   
  56. PL/SQL procedure successfully completed. 
  57.   
  58. SQL> 

上面脚本构造了测试用的例子和数据, 那么我们对比看看外键有无索引的区别:

  1. SQL> set linesize 1200 
  2. SQL> set autotrace traceonly 
  3. SQL> select p.id , p.name,c.child_name 
  4.   2  from test.parent_tb_test p 
  5.   3  inner join test.child_tb_test c on p.id = c.f_id  
  6.   4  where p.id=1000; 
  7.   
  8. 1000 rows selected. 
  9.   
  10.   
  11. Execution Plan 
  12. ---------------------------------------------------------- 
  13. Plan hash value: 901213199 
  14.   
  15. -------------------------------------------------------------------------------------------------- 
  16. | Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)| Time     | 
  17. -------------------------------------------------------------------------------------------------- 
  18. |   0 | SELECT STATEMENT             |                   |  1009 | 44396 |  4706  (21)| 00:00:07 | 
  19. |   1 |  NESTED LOOPS                |                   |  1009 | 44396 |  4706  (21)| 00:00:07 | 
  20. |   2 |   TABLE ACCESS BY INDEX ROWID| PARENT_TB_TEST    |     1 |    31 |     1   (0)| 00:00:01 | 
  21. |*  3 |    INDEX UNIQUE SCAN         | PK_PARENT_TB_TEST |     1 |       |     1   (0)| 00:00:01 | 
  22. |*  4 |   TABLE ACCESS FULL          | CHILD_TB_TEST     |  1009 | 13117 |  4705  (21)| 00:00:07 | 
  23. -------------------------------------------------------------------------------------------------- 
  24. Predicate Information (identified by operation id): 
  25. --------------------------------------------------- 
  26.    3 - access("P"."ID"=1000) 
  27.    4 - filter("C"."F_ID"=1000) 
  28.   
  29.   
  30. Statistics 
  31. ---------------------------------------------------------- 
  32.           0  recursive calls 
  33.           0  db block gets 
  34.       32855  consistent gets 
  35.       32772  physical reads 
  36.           0  redo size 
  37.       29668  bytes sent via SQL*Net to client 
  38.        1218  bytes received via SQL*Net from client 
  39.          68  SQL*Net roundtrips to/from client 
  40.           0  sorts (memory) 
  41.           0  sorts (disk) 
  42.        1000  rows processed 
  43.   
  44. SQL>  

创建索引后,我们再来看看其执行计划,注意对比创建索引前后,执行计划的差异,如下所示:

  1. SQL> create index ix_child_tb_test on child_tb_test(f_id); 
  2.   
  3. SQL> set linesize 1200 
  4. SQL> set autotrace traceonly 
  5. SQL> select p.id , p.name,c.child_name 
  6.   2  from test.parent_tb_test p 
  7.   3  inner join test.child_tb_test c on p.id = c.f_id  
  8.   4  where p.id=1000; 

接下来,我们再来看看外键缺失索引影响并发,以及造成死锁的情况,如下所示,创建表dead_lock_parent与dead_lock_foreign,两者存在主外键关系,分布插入两条测试数据:

  1. SQL> create table dead_lock_parent( id number primary keyname varchar2(32)); 
  2.   
  3. Table created. 
  4.   
  5. SQL> create table dead_lock_foreign(fid  number, fname varchar2(32), foreign key(fid) references dead_lock_parent); 
  6.   
  7. Table created. 
  8.   
  9. SQL> insert into dead_lock_parent values( 1, 'kerry'); 
  10.   
  11. 1 row created. 
  12.   
  13. SQL> insert into dead_lock_foreign values(1, 'kerry_fk');   
  14.   
  15. 1 row created. 
  16.   
  17. SQL> insert into dead_lock_parent values(2, 'jimmy'); 
  18.   
  19. 1 row created. 
  20.   
  21. SQL> insert into dead_lock_foreign values(2, 'jimmy_fk'); 
  22.   
  23. 1 row created. 
  24.   
  25. SQL> commit
  26.   
  27. Commit complete. 
  28.   
  29. SQL>  

1:在会话1(会话ID为789)里面执行下面SQL语句:

  1. SQL> show user
  2. USER 为 "TEST" 
  3. SQL> select * from v$mystat where rownum=1; 
  4.   
  5.        SID STATISTIC#      VALUE 
  6. ---------- ---------- ---------- 
  7.        789          0          1 
  8.   
  9. SQL> delete from dead_lock_foreign where fid=1; 
  10.   
  11. 已删除 1 行。 

2:在会话2(会话ID为766)里面执行下面SQL语句:

  1. SQL> show user
  2. USER is "TEST" 
  3. SQL> select * from v$mystat where rownum=1; 
  4.   
  5.        SID STATISTIC#      VALUE 
  6. ---------- ---------- ---------- 
  7.        766          0          1 
  8.   
  9. SQL> delete from dead_lock_foreign where fid=2; 
  10.   
  11. 1 row deleted. 

3:接着在会话1(会话ID为789)里执行删除dead_lock_parent中id为1的记录:

  1. SQL> delete from dead_lock_parent where id=1; 

此时你会发现会话被阻塞了,我们可以用下面SQL查询具体的阻塞信息。

  1. COL MODE_HELD FOR A14; 
  2. COL LOCK_TYPE FOR A8; 
  3. COL MODE_REQUESTED FOR A10; 
  4. COL OBJECT_TYPE FOR A14; 
  5. COL OBJECT_NAME FOR A20; 
  6. SELECT LK.SID, 
  7.        DECODE(LK.TYPE, 
  8.               'TX'
  9.               'Transaction'
  10.               'TM'
  11.               'DML'
  12.               'UL'
  13.               'PL/SQL User Lock'
  14.               LK.TYPE) LOCK_TYPE, 
  15.        DECODE(LK.LMODE, 
  16.               0, 
  17.               'None'
  18.               1, 
  19.               'Null'
  20.               2, 
  21.               'Row-S (SS)'
  22.               3, 
  23.               'Row-X (SX)'
  24.               4, 
  25.               'Share'
  26.               5, 
  27.               'S/Row-X (SSX)'
  28.               6, 
  29.               'Exclusive'
  30.               TO_CHAR(LK.LMODE)) MODE_HELD, 
  31.        DECODE(LK.REQUEST, 
  32.               0, 
  33.               'None'
  34.               1, 
  35.               'Null'
  36.               2, 
  37.               'Row-S (SS)'
  38.               3, 
  39.               'Row-X (SX)'
  40.               4, 
  41.               'Share'
  42.               5, 
  43.               'S/Row-X (SSX)'
  44.               6, 
  45.               'Exclusive'
  46.               TO_CHAR(LK.REQUEST)) MODE_REQUESTED,  
  47.        OB.OBJECT_TYPE, 
  48.        OB.OBJECT_NAME, 
  49.        LK.BLOCK, 
  50.        SE.LOCKWAIT 
  51.   FROM V$LOCK LK, DBA_OBJECTS OB, V$SESSION SE 
  52.  WHERE LK.TYPE IN ('TM''UL'
  53.    AND LK.SID = SE.SID 
  54.    AND LK.ID1 = OB.OBJECT_ID(+) 
  55.  AND SE.SID IN (766,789) 
  56.  ORDER BY SID; 

上面信息如果不能让你理解,那么可以看看下面脚本,相信你能看得更详细。

  1. SQL> SELECT S.SID                             SID,  
  2.             S.USERNAME                        USERNAME,  
  3.             S.MACHINE                         MACHINE,  
  4.             L.TYPE                            TYPE,  
  5.             O.OBJECT_NAME                     OBJECT_NAME,  
  6.          DECODE(L.LMODE, 0, 'None',  
  7.                          1, 'Null',  
  8.                          2, 'Row Share',  
  9.                          3, 'Row Exlusive',  
  10.                          4, 'Share',  
  11.                          5, 'Sh/Row Exlusive',  
  12.                          6, 'Exclusive')   lmode,  
  13.          DECODE(L.REQUEST, 0, 'None',  
  14.                            1, 'Null',  
  15.                            2, 'Row Share',  
  16.                            3, 'Row Exlusive',  
  17.                            4, 'Share',  
  18.                            5, 'Sh/Row Exlusive',  
  19.                            6, 'Exclusive') request,  
  20.          L.BLOCK                           BLOCK  
  21.   FROM   V$LOCK L,  
  22.          V$SESSION S,  
  23.          DBA_OBJECTS O  
  24.   WHERE  L.SID = S.SID  
  25.          AND USERNAME != 'SYSTEM'  
  26.          AND O.OBJECT_ID(+) = L.ID1  
  27.          AND S.SID IN ( 766,789)  
  28.   ORDER  BY S.SID;  
  29.   
  30.        SID USERNAME MACHINE        TY OBJECT_NAME          LMODE           REQUEST         BLOCK 
  31. ---------- -------- -------------- -- -------------------- --------------- --------------- ----- 
  32.        766 TEST     XXXX\GET253194 TX                      Exclusive       None                0 
  33.        766 TEST     XXXX\GET253194 TM DEAD_LOCK_FOREIGN    Row Exlusive    None                1 
  34.        766 TEST     XXXX\GET253194 TM DEAD_LOCK_PARENT     Row Exlusive    None                0 
  35.        789 TEST     DB-Server.loca TX                      Exclusive       None                0 
  36.                     ldomain 
  37.   
  38.        789 TEST     DB-Server.loca TM DEAD_LOCK_PARENT     Row Exlusive    None                0 
  39.                     ldomain 
  40.   
  41.        789 TEST     DB-Server.loca TM DEAD_LOCK_FOREIGN    Row Exlusive    Sh/Row Exlusive     0 
  42.                     ldomain     

接着在会话2里面执行下面SQL,删除主表中id=2的记录

  1. SQL> delete from dead_lock_parent where id=2; 

你会发现会话1就会出现Deadlock

如果你在外键字段上创建索引,那么这种情况下的操作就不会出现死锁。在这里就不再赘述。有兴趣可以测试一下.

外键创建索引建议(Foreign Key Indexing Tips)

虽然增加索引,可能会带来一些额外的性能开销(DML操作开销增加)和磁盘空间方面的开销,但是相比其带来的性能改善而言,这些额外的开销其实完全可以忽略。如果没有其他特殊情况,建议所有的外键字段都加上索引。在Oracle Oracle Database 9i/10g/11g编程艺术这本书中介绍了在什么时候不需要对外键加索引. 必须满足下面三个条件:

  1. 不会删除父表中的行。
  2. 不论是有意还是无意,总之不会更新父表的唯一/主键字段值。
  3. 不会从父表联结到子表, 或者更通俗的讲,外键列不支持子表的一个重要访问路径,而且你在谓词中没有使用这些外键累从子表中选择数据。

找出未索引的外键

我们首先可以通过下面脚本,找到整个数据库中那些表有主外键关系,并列出主外键约束.

--查看整个数据库下拥有主外键关系的所有表(排除一些系统用户)

  1. --查看整个数据库下拥有主外键关系的所有表(排除一些系统用户)    
  2. SELECT DC.OWNER                   AS "PARENT_TABLE_OWNER",  
  3.        DC.TABLE_NAME              AS "PARENT_TABLE_NAME",  
  4.        DC.CONSTRAINT_NAME         AS "PRIMARY CONSTRAINT NAME",  
  5.        DF.CONSTRAINT_NAME         AS "REFERENCED CONSTRAINT NAME",  
  6.        DF.OWNER                   AS "CHILD_TABLE_OWNER",  
  7.        DF.TABLE_NAME              AS "CHILD_TABLE_NAME"  
  8. FROM   DBA_CONSTRAINTS DC,  
  9.        (SELECT C.OWNER,  
  10.                C.CONSTRAINT_NAME,  
  11.                C.R_CONSTRAINT_NAME,  
  12.                C.TABLE_NAME  
  13.         FROM   DBA_CONSTRAINTS C  
  14.         WHERE  CONSTRAINT_TYPE = 'R') DF  
  15. WHERE  DC.CONSTRAINT_NAME =DF.R_CONSTRAINT_NAME  
  16.        AND DC.OWNER NOT IN ( 'SYSTEM''SYS''DBSNMP''EXFSYS',  
  17.                             'ORDDATA''CTXSYS''OLAPSYS''MDSYS',  
  18.                             'SYSMAN' );  

--查看某个Schema下拥有主外键关系的所有表

  1. --查看某个Schema下拥有主外键关系的所有表    
  2. SELECT DC.OWNER           AS "PARENT_TABLE_OWNER",  
  3.        DC.TABLE_NAME      AS "PARENT_TABLE_NAME",  
  4.        DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME",  
  5.        DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME",  
  6.        DF.OWNER           AS "CHILD_TABLE_OWNER",  
  7.        DF.TABLE_NAME      AS "CHILD_TABLE_NAME"  
  8. FROM   DBA_CONSTRAINTS DC,  
  9.        (SELECT C.OWNER,  
  10.                C.CONSTRAINT_NAME,  
  11.                C.R_CONSTRAINT_NAME,  
  12.                C.TABLE_NAME  
  13.         FROM   DBA_CONSTRAINTS C  
  14.         WHERE  CONSTRAINT_TYPE = 'R') DF  
  15. WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME  
  16.        AND DC.OWNER =UPPER('&OWNER');   

--查看某个具体的表是否和其它表拥有主外键关系

  1. --查看某个具体的表是否和其它表拥有主外键关系   
  2. SELECT DC.OWNER           AS "PARENT_TABLE_OWNER",  
  3.        DC.TABLE_NAME      AS "PARENT_TABLE_NAME",  
  4.        DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME",  
  5.        DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME",  
  6.        DF.OWNER           AS "CHILD_TABLE_OWNER",  
  7.        DF.TABLE_NAME      AS "CHILD_TABLE_NAME"  
  8. FROM   DBA_CONSTRAINTS DC,  
  9.        (SELECT C.OWNER,  
  10.                C.CONSTRAINT_NAME,  
  11.                C.R_CONSTRAINT_NAME,  
  12.                C.TABLE_NAME  
  13.         FROM   DBA_CONSTRAINTS C  
  14.         WHERE  CONSTRAINT_TYPE = 'R') DF  
  15. WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME  
  16.        AND DC.OWNER =UPPER('&OWNER'
  17.        AND DC.TABLE_NAME=UPPER('&TABLE_NAME'); 

接下来我们要找出在具体的外键字段是否有索引,脚本如下所示:

  1. SELECT   CON.OWNER , 
  2.          CON.TABLE_NAME, 
  3.          CON.CONSTRAINT_NAME, 
  4.          CON.COL_LIST, 
  5.          'No Indexed' AS INDEX_STATUS 
  6. FROM 
  7.      (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME, 
  8.            MAX(DECODE(POSITION, 1,     '"' || 
  9.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  10.            MAX(DECODE(POSITION, 2,', '||'"'|| 
  11.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  12.            MAX(DECODE(POSITION, 3,', '||'"'|| 
  13.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  14.            MAX(DECODE(POSITION, 4,', '||'"'|| 
  15.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  16.            MAX(DECODE(POSITION, 5,', '||'"'|| 
  17.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  18.            MAX(DECODE(POSITION, 6,', '||'"'|| 
  19.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  20.            MAX(DECODE(POSITION, 7,', '||'"'|| 
  21.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  22.            MAX(DECODE(POSITION, 8,', '||'"'|| 
  23.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  24.            MAX(DECODE(POSITION, 9,', '||'"'|| 
  25.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  26.            MAX(DECODE(POSITION, 10,', '||'"'|| 
  27.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST 
  28.            FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC 
  29.            WHERE DC.OWNER = CC.OWNER 
  30.            AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME 
  31.            AND DC.CONSTRAINT_TYPE = 'R' 
  32.            AND DC.OWNER NOT IN ('SYS''SYSTEM''OLAPSYS''SYSMAN''MDSYS''ADMIN'
  33.            GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME  
  34.      ) CON 
  35.       WHERE NOT EXISTS ( 
  36.         SELECT 1 FROM 
  37.                   ( SELECT TABLE_OWNER, TABLE_NAME,    
  38.                        MAX(DECODE(COLUMN_POSITION, 1,     '"'|| 
  39.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  40.                        MAX(DECODE(COLUMN_POSITION, 2,', '||'"'|| 
  41.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  42.                        MAX(DECODE(COLUMN_POSITION, 3,', '||'"'|| 
  43.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  44.                        MAX(DECODE(COLUMN_POSITION, 4,', '||'"'|| 
  45.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  46.                        MAX(DECODE(COLUMN_POSITION, 5,', '||'"'|| 
  47.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  48.                        MAX(DECODE(COLUMN_POSITION, 6,', '||'"'|| 
  49.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  50.                        MAX(DECODE(COLUMN_POSITION, 7,', '||'"'|| 
  51.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  52.                        MAX(DECODE(COLUMN_POSITION, 8,', '||'"'|| 
  53.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  54.                        MAX(DECODE(COLUMN_POSITION, 9,', '||'"'|| 
  55.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  56.                        MAX(DECODE(COLUMN_POSITION, 10,', '||'"'|| 
  57.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST 
  58.                        FROM DBA_IND_COLUMNS  
  59.                    WHERE TABLE_OWNER NOT IN ('SYS''SYSTEM''OLAPSYS''SYSMAN''MDSYS'
  60.                    GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL 
  61.     WHERE CON.OWNER = COL.TABLE_OWNER  
  62.     AND CON.TABLE_NAME = COL.TABLE_NAME   
  63.     AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ;   

如果是ORACLE 11g或以上版本,数据库有分析函数LISTAGG的话,可以使用下面脚本

  1. SELECT CASE  
  2.          WHEN B.TABLE_NAME IS NULL THEN 'NO INDEXED'  
  3.          ELSE 'INDEXED'  
  4.        END               AS STATUS,  
  5.        A.TABLE_OWNER     AS TABLE_OWNER,  
  6.        A.TABLE_NAME      AS TABLE_NAME,  
  7.        A.CONSTRAINT_NAME AS FK_NAME,  
  8.        A.FK_COLUMNS      AS FK_COLUMNS,  
  9.        B.INDEX_NAME      AS INDEX_NAME,  
  10.        B.INDEX_COLUMNS   AS INDEX_COLUMNS  
  11. FROM   (SELECT A.OWNER                              AS TABLE_OWNER,  
  12.                A.TABLE_NAME                         AS TABLE_NAME,  
  13.                A.CONSTRAINT_NAME                    AS CONSTRAINT_NAME,  
  14.                LISTAGG(A.COLUMN_NAME, ',')  
  15.                  WITHIN GROUP (ORDER BY A.POSITION) FK_COLUMNS  
  16.         FROM   DBA_CONS_COLUMNS A,  
  17.                DBA_CONSTRAINTS B  
  18.         WHERE  A.CONSTRAINT_NAME = B.CONSTRAINT_NAME  
  19.                AND B.CONSTRAINT_TYPE = 'R'  
  20.                AND A.OWNER = B.OWNER  
  21.                AND A.OWNER NOT IN ( 'SYS''SYSTEM''OLAPSYS''SYSMAN',  
  22.                                     'MDSYS' )  
  23.         GROUP  BY A.OWNER,  
  24.                   A.TABLE_NAME,  
  25.                   A.CONSTRAINT_NAME) A,  
  26.        (SELECT TABLE_OWNER,  
  27.                TABLE_NAME,  
  28.                INDEX_NAME,  
  29.                LISTAGG(C.COLUMN_NAME, ',')  
  30.                  WITHIN GROUP (ORDER BY C.COLUMN_POSITION) INDEX_COLUMNS  
  31.         FROM   DBA_IND_COLUMNS C  
  32.         GROUP  BY TABLE_OWNER,  
  33.                   TABLE_NAME,  
  34.                   INDEX_NAME) B  
  35. WHERE  A.TABLE_NAME = B.TABLE_NAME(+)  
  36.        AND A.TABLE_OWNER = B.TABLE_OWNER(+)  
  37.        AND B.INDEX_COLUMNS(+) LIKE A.FK_COLUMNS  
  38.                                    || '%'  
  39. ORDER  BY 1 DESC 

自动生成创建外键索引的脚本

上面的这些脚本已经能找出那些外键字段已经建立或未建立索引,此时如果对外键字段缺少索引的表手工创建索引的话,如果数量很多的话,那么工作量也非常大,下面可以用这个脚本自动生成缺失的索引

  1. /******************************************************************************************* 
  2. --脚本功能描述: 
  3. --  对于数据库中外键缺少索引的字段,生成对应的索引(排除一些系统账号,例如sys、system),如果外键索引超过十个字段 
  4. --  那么这个脚本就不能正确的生成对应的索引,当然也很少有外键设置在超过10个字段的。另外索引表空 
  5. --  空间跟数据表空间相同,如有分开的话,建议在此处再做调整。 
  6. ********************************************************************************************/ 
  7. SELECT    'CREATE INDEX ' || OWNER || '.' || REPLACE(CONSTRAINT_NAME,'FK_','IX_') ||  
  8.         ' ON ' || OWNER || '.' || TABLE_NAME || ' (' || COL_LIST ||') TABLESPACE '  
  9.         || (SELECT TABLESPACE_NAME FROM DBA_TABLES WHERE OWNER= CON.OWNER AND TABLE_NAME= CON.TABLE_NAME)  
  10.         AS  CREATE_INDEXES_ON_FOREIGN_KEY  
  11. FROM 
  12.      (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME, 
  13.            MAX(DECODE(POSITION, 1,     '"' || 
  14.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  15.            MAX(DECODE(POSITION, 2,', '||'"'|| 
  16.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  17.            MAX(DECODE(POSITION, 3,', '||'"'|| 
  18.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  19.            MAX(DECODE(POSITION, 4,', '||'"'|| 
  20.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  21.            MAX(DECODE(POSITION, 5,', '||'"'|| 
  22.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  23.            MAX(DECODE(POSITION, 6,', '||'"'|| 
  24.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  25.            MAX(DECODE(POSITION, 7,', '||'"'|| 
  26.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  27.            MAX(DECODE(POSITION, 8,', '||'"'|| 
  28.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  29.            MAX(DECODE(POSITION, 9,', '||'"'|| 
  30.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) || 
  31.            MAX(DECODE(POSITION, 10,', '||'"'|| 
  32.                   SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST 
  33.            FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC 
  34.            WHERE DC.OWNER = CC.OWNER 
  35.            AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME 
  36.            AND DC.CONSTRAINT_TYPE = 'R' 
  37.            AND DC.OWNER NOT IN ('SYS''SYSTEM''OLAPSYS''SYSMAN''MDSYS''ADMIN'
  38.            GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME  
  39.      ) CON 
  40.       WHERE NOT EXISTS ( 
  41.         SELECT 1 FROM 
  42.                   ( SELECT TABLE_OWNER, TABLE_NAME,    
  43.                        MAX(DECODE(COLUMN_POSITION, 1,     '"'|| 
  44.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  45.                        MAX(DECODE(COLUMN_POSITION, 2,', '||'"'|| 
  46.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  47.                        MAX(DECODE(COLUMN_POSITION, 3,', '||'"'|| 
  48.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  49.                        MAX(DECODE(COLUMN_POSITION, 4,', '||'"'|| 
  50.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  51.                        MAX(DECODE(COLUMN_POSITION, 5,', '||'"'|| 
  52.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  53.                        MAX(DECODE(COLUMN_POSITION, 6,', '||'"'|| 
  54.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  55.                        MAX(DECODE(COLUMN_POSITION, 7,', '||'"'|| 
  56.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  57.                        MAX(DECODE(COLUMN_POSITION, 8,', '||'"'|| 
  58.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  59.                        MAX(DECODE(COLUMN_POSITION, 9,', '||'"'|| 
  60.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) || 
  61.                        MAX(DECODE(COLUMN_POSITION, 10,', '||'"'|| 
  62.                               SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST 
  63.                        FROM DBA_IND_COLUMNS  
  64.                    WHERE TABLE_OWNER NOT IN ('SYS''SYSTEM''OLAPSYS''SYSMAN''MDSYS'
  65.                    GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL 
  66.     WHERE CON.OWNER = COL.TABLE_OWNER  
  67.     AND CON.TABLE_NAME = COL.TABLE_NAME   
  68.     AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ;  

--脚本使用分析函数LISTAGG, 适用于ORACLE 11g以及以上版本,如果数据库版本是Oracle 11g及以上,就可以使用此脚本替代上面脚本。

  1. SELECT 'CREATE INDEX '  
  2.               || OWNER  
  3.               || '.'  
  4.               || REPLACE(CONSTRAINT_NAME,'FK_','IX_')  
  5.               || ' ON '  
  6.               || OWNER  
  7.               || '.'  
  8.               || TABLE_NAME  
  9.               || ' ('  
  10.               || FK_COLUMNS  
  11.               ||') TABLESPACE '  
  12.               ||  
  13.        (  
  14.               SELECT TABLESPACE_NAME  
  15.               FROM   DBA_TABLES  
  16.               WHERE  OWNER= CON.OWNER  
  17.               AND    TABLE_NAME= CON.TABLE_NAME) CREATE_INDEXES_ON_FOREIGN_KEY  
  18. FROM   (  
  19.                 SELECT   CC.OWNER,  
  20.                          CC.TABLE_NAME,  
  21.                          CC.CONSTRAINT_NAME,  
  22.                          LISTAGG(CC.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY CC.POSITION) FK_COLUMNS 
  23.                 FROM     DBA_CONS_COLUMNS CC,  
  24.                          DBA_CONSTRAINTS DC  
  25.                 WHERE    CC.CONSTRAINT_NAME = DC.CONSTRAINT_NAME  
  26.                 AND      DC.CONSTRAINT_TYPE = 'R'  
  27.                 AND      CC.OWNER = DC.OWNER  
  28.                 AND      DC.OWNER NOT IN ( 'SYS',  
  29.                                           'SYSTEM',  
  30.                                           'OLAPSYS',  
  31.                                           'SYSMAN',  
  32.                                           'MDSYS',  
  33.                                           'ADMIN' )  
  34.                 GROUP BY CC.OWNER,  
  35.                          CC.TABLE_NAME,  
  36.                          CC.CONSTRAINT_NAME) CON  
  37.   WHERE NOT EXISTS  
  38.        (  
  39.               SELECT 1  
  40.               FROM   (  
  41.                               SELECT   TABLE_OWNER,  
  42.                                        TABLE_NAME,  
  43.                                        INDEX_NAME, 
  44.                                        LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_POSITION) FK_COLUMNS 
  45.                               FROM     DBA_IND_COLUMNS  
  46.                               WHERE    INDEX_OWNER NOT IN ( 'SYS',  
  47.                                                            'SYSTEM',  
  48.                                                            'OLAPSYS',  
  49.                                                            'SYSMAN',  
  50.                                                            'MDSYS',  
  51.                                                            'ADMIN' )  
  52.                                                           
  53.                               GROUP BY TABLE_OWNER,  
  54.                                        TABLE_NAME ,INDEX_NAME) COL  
  55.               WHERE  CON.OWNER = COL.TABLE_OWNER  
  56.               AND    CON.TABLE_NAME = COL.TABLE_NAME  
  57.               AND    CON.FK_COLUMNS = SUBSTR(COL.FK_COLUMNS, 1, LENGTH(CON.FK_COLUMNS)) )  
  58.               ORDER BY 1; 

参考资料:

http://www.dba-oracle.com/t_foreign_key_indexing.htm


本文转载自网络,原文链接:https://mp.weixin.qq.com/s/_bQBQstlhsy8HdBikcTbog
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐