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

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

CREATE INDEX SCOTT.IX_DEPTNO ON SCOTT.EMP (“DEPTNO”) TABLESPACE USERS; 
 
当然这两个表的数据量实在是太少了,性能上差别不大,当数据量增长上去后,这个性能差异就会比较明显了。如下例子所示,我们构造一个数据量相对较大的父表与子表:

  1. create table parent_tb_test
  2. (
  3. id number(10),
  4. name varchar2(32),
  5. constraint pk_parent_tb_test primary key(id)
  6. );
  7.  
  8. create table child_tb_test
  9. (
  10. c_id number(10),
  11. f_id number(10),
  12. child_name varchar2(32),
  13. constraint pk_child_tb_test primary key(c_id),
  14. foreign key(f_id) references parent_tb_test
  15. );
  16.  
  17. begin
  18.  
  19. for index_num in 1 .. 10000 loop
  20. insert into parent_tb_test
  21. select index_num , 'kerry' || to_char(index_num) from dual;
  22.  
  23. if mod(index_num,100) = 0 then
  24. commit;
  25. end if;
  26. end loop;
  27.  
  28. commit;
  29.  
  30. end;
  31. /
  32.  
  33. declare index_num number :=1;
  34. begin
  35.  
  36. for index_parent in 1 .. 10000 loop
  37. for index_child in 1 .. 1000 loop
  38. insert into child_tb_test
  39. select index_num, index_parent, 'child' || to_char(index_child) from dual;
  40.  
  41. index_num := index_num +1;
  42. if mod(index_child,1000) = 0 then
  43. commit;
  44. end if;
  45. end loop;
  46. end loop;
  47.  
  48. commit;
  49. end;
  50. /

  1. 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');
  2. PL/SQL procedure successfully completed.
  3. 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');
  4. PL/SQL procedure successfully completed.
  5. 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. 1000 rows selected.
  8. Execution Plan
  9. ----------------------------------------------------------
  10. Plan hash value: 901213199
  11. --------------------------------------------------------------------------------------------------
  12. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  13. --------------------------------------------------------------------------------------------------
  14. | 0 | SELECT STATEMENT | | 1009 | 44396 | 4706 (21)| 00:00:07 |
  15. | 1 | NESTED LOOPS | | 1009 | 44396 | 4706 (21)| 00:00:07 |
  16. | 2 | TABLE ACCESS BY INDEX ROWID| PARENT_TB_TEST | 1 | 31 | 1 (0)| 00:00:01 |
  17. |* 3 | INDEX UNIQUE SCAN | PK_PARENT_TB_TEST | 1 | | 1 (0)| 00:00:01 |
  18. |* 4 | TABLE ACCESS FULL | CHILD_TB_TEST | 1009 | 13117 | 4705 (21)| 00:00:07 |
  19. --------------------------------------------------------------------------------------------------
  20. Predicate Information (identified by operation id):
  21. ---------------------------------------------------
  22. 3 - access("P"."ID"=1000)
  23. 4 - filter("C"."F_ID"=1000)
  24. Statistics
  25. ----------------------------------------------------------
  26. 0 recursive calls
  27. 0 db block gets
  28. 32855 consistent gets
  29. 32772 physical reads
  30. 0 redo size
  31. 29668 bytes sent via SQL*Net to client
  32. 1218 bytes received via SQL*Net from client
  33. 68 SQL*Net roundtrips to/from client
  34. 0 sorts (memory)
  35. 0 sorts (disk)
  36. 1000 rows processed
  37. SQL>

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

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

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

  1. SQL> create table dead_lock_parent( id number primary key, name varchar2(32));
  2. Table created.
  3. SQL> create table dead_lock_foreign(fid number, fname varchar2(32), foreign key(fid) references dead_lock_parent);
  4. Table created.
  5. SQL> insert into dead_lock_parent values( 1, 'kerry');
  6. 1 row created.
  7. SQL> insert into dead_lock_foreign values(1, 'kerry_fk');
  8. 1 row created.
  9. SQL> insert into dead_lock_parent values(2, 'jimmy');
  10. 1 row created.
  11. SQL> insert into dead_lock_foreign values(2, 'jimmy_fk');
  12. 1 row created.
  13. SQL> commit;
  14. Commit complete.
  15. SQL>

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

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

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

  1. SQL> show user;
  2. USER is "TEST"
  3. SQL> select * from v$mystat where rownum=1;
  4. SID STATISTIC# VALUE
  5. ---------- ---------- ----------
  6. 766 0 1
  7. SQL> delete from dead_lock_foreign where fid=2;
  8. 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. SID USERNAME MACHINE TY OBJECT_NAME LMODE REQUEST BLOCK
  30. ---------- -------- -------------- -- -------------------- --------------- --------------- -----
  31. 766 TEST XXXX\GET253194 TX Exclusive None 0
  32. 766 TEST XXXX\GET253194 TM DEAD_LOCK_FOREIGN Row Exlusive None 1
  33. 766 TEST XXXX\GET253194 TM DEAD_LOCK_PARENT Row Exlusive None 0
  34. 883 TEST DB-Server.loca TX Exclusive None 0
  35. ldomain
  36. 883 TEST DB-Server.loca TM DEAD_LOCK_PARENT Row Exlusive None 0
  37. ldomain
  38. 883 TEST DB-Server.loca TM DEAD_LOCK_FOREIGN Row Exlusive Sh/Row Exlusive 0
  39. ldomain

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

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

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

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

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

1: 不会删除父表中的行。

2: 不论是有意还是无意,总之不会更新父表的唯一/主键字段值。

3: 不会从父表联结到子表, 或者更通俗的讲,外键列不支持子表的一个重要访问路径,而且你在谓词中没有使用这些外键累从子表中选择数据。

找出未索引的外键

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

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

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

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

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

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

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

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

  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;

(转)ORACLE中关于外键缺少索引的探讨和总结的更多相关文章

  1. ORACLE中关于外键缺少索引的探讨和总结

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

  2. Oracle外键不加索引会引起死锁问题

    转载链接:http://www.jb51.net/article/50161.htm 这篇文章主要介绍了Oracle外键不加索引引起死锁的情况及解决,需要的朋友可以参考下 --创建一个表,此表作为子表 ...

  3. oracle 查看主外键约束

    select a.constraint_name, a.table_name, b.constraint_name from user_constraints a, user_constraints ...

  4. [原创]MYSQL中利用外键实现级联删除和更新

    MySQL中利用外键实现级联删除.更新 MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引.在创建索引的时候,可以指定 ...

  5. mysql中主外键关系

    一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, ...

  6. Oracle系统表外键的更名

    正在看的ORACLE教程是:Oracle系统表外键的更名. Oracle中表的外键是保证系统参照完整性的手段,而参照完整性是指分布在两个表中的列所满足的具有主从性质的约束关系.外键涉及到两个表,其中一 ...

  7. mysql|中主外键关系(转)

    http://my.oschina.net/liting/blog/356150 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标 ...

  8. sql-主键、外键、索引

    SQL的主键和外键的作用: 外键取值规则:空值或参照的主键值. (1)插入非空值时,如果主键表中没有这个值,则不能插入. (2)更新时,不能改为主键表中没有的值. (3)删除主键表记录时,你可以在建外 ...

  9. SET FOREIGN_KEY_CHECKS=0;在Mysql中取消外键约束。

    SET FOREIGN_KEY_CHECKS=0;在Mysql中取消外键约束.

随机推荐

  1. AP注册

    1.ac发现ap 两种模式:二层发现.三层发现 按ap与ac所处ip网段不同,可以把注册过程分为二层模式和三层模式: 两种模式均通过发送discovery报文进行,二层模式discovery报文仅在同 ...

  2. python3 速查参考- python基础 9 -> MySQL基础概念、数据库create、alter、insert、update、delete、select等基础命令

    前置步骤: 下载一个绿色版的mysql数据库客户端连接工具 :http://wosn.net/821.html mysql平台为win7(以后会有CentOS上的) 学习目的: 掌握数据库的基本概念, ...

  3. [转帖]VIM常用快捷键

    VIM常用快捷键 https://www.cnblogs.com/markleaf/p/7808817.html 快捷键能够显著的提高工作效率 一.移动光标 h,j,k,l 上,下,左,右ctrl-e ...

  4. CENTOS 6-7的本地YUM源配置

    本文档适合CENTOS 6-7的本地YUM源配置 cd /media cd CentOS_6.8_Final/ cd Packages 创建目录拷贝文件 mkdir /yum cp * /yum 配置 ...

  5. P1417 烹调方案(思维+01背包)

    (点击此处查看原题) 题意 有n种食材,每种食材有三个属性,ai,bi和ci,如果在t时刻完成第i样食材则得到ai-t*bi的美味指数,用第i件食材做饭要花去ci的时间.问在T时间内,什么样的烹调方案 ...

  6. PDO原生分页

    ** PDO分页** 1.PDO连接数据库 $dbh=new PDO('mysql:host=127.0.0.1;dbname=03a','root','root');//使用pdo 2.接收页码 $ ...

  7. pip安装源

    目录 介绍 永久配置安装源 Windows MacOS.Linux 配置文件内容 介绍 """ 1.采用国内源,加速下载模块的速度 2.常用pip源: -- 豆瓣:htt ...

  8. jwt认证规则

    目录 认证规则图 django不分离 drf分类 认证规则演变图 数据库session认证:低效 缓存认证:高效 jwt认证:高效 缓存认证:不易并发 jwt认证:易并发 JWT认证规则 优点 格式 ...

  9. hadoop-InputFormat-Split-任务并行度

    首先来看 MapReduce 流程图 一个 map,一个 reduce,中间靠 shuffle 连接,shuffle 左边被划分到 map,右边被划分到 reduce InputFormat inpu ...

  10. 树莓派和STM32通过USB和串口通信记录

    不管怎样,为了简便开发,通信选择串口通信. 推荐文章:https://blog.csdn.net/magnetoooo/article/details/53564797 推荐测试工具:https:// ...