ORACLE中關於外鍵缺乏索引的探討和總結

    在ORACLE數據庫中,定義外鍵約束時,ORACLE是不會自動建立對應索引的,必須手動在外鍵約束相關的列上建立索引。那麼外鍵字段上是否有必要建立索引呢若是有必要的話,巡檢時,如何找出外鍵字段上沒有建立索引的相關表,並生成對應的索引的腳本呢? 數據庫

 

外鍵缺失索引影響 編程

 

外鍵列上缺乏索引會帶來三個問題,限制併發性、影響性能、還有可能形成死鎖。因此對於絕大部分場景,咱們應該儘可能考慮在外鍵上面建立索引 併發

 

1. 影響性能。 若是子表外鍵沒有建立索引,那麼當父表查詢關聯子表時,子表將進行全表掃描。影響錶鏈接方式。 oracle

2. 影響併發。 不管是更新父表主鍵,或者刪除一個父記錄,都會在子表中加一個表鎖(在這條語句完成前,不容許對子表作任何修改)。這就會沒必要要 app

地鎖定更多的行,而影響併發性 dom

3:在特殊狀況下,還有可能形成死鎖。 ide

 

咱們先來看看一個簡單的例子,看看當外鍵缺失索引時,子表是否進行全表掃描,以下所示,表EMP與DEPT存在主外鍵關係:函數

SQL> set autotrace on;
SQL> 
SQL> SELECT  D.DEPTNO, COUNT(*)
  2  FROM SCOTT.EMP E INNER JOIN SCOTT.DEPT D ON E.DEPTNO =D.DEPTNO
  3  GROUP BY D.DEPTNO;
 
    DEPTNO   COUNT(*)
---------- ----------
        30          6
        20          5
        10          3
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4067220884
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |     9 |     4  (25)| 00:00:01 |
|   1 |  HASH GROUP BY     |      |     3 |     9 |     4  (25)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| EMP  |    14 |    42 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("E"."DEPTNO" IS NOT NULL)
 
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        665  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

clip_image001

 

如上所示,當外鍵字段沒有索引時,父表與子表關聯時,子表會進行全表掃描,下面,我在外鍵字段建立索引後,就能避免子表表掃描了。oop

 

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

 

clip_image002

 

 

固然這兩個表的數據量實在是太少了,性能上差異不大,當數據量增加上去後,這個性能差別就會比較明顯了。以下例子所示,咱們構造一個數據量相對較大的父表與子表:性能

 

create table parent_tb_test 
(
    id         number(10),
    name    varchar2(32),
    constraint pk_parent_tb_test primary key(id)
);
 
create table child_tb_test
(
     c_id   number(10),
     f_id        number(10),
     child_name    varchar2(32),
     constraint pk_child_tb_test primary key(c_id),
     foreign key(f_id) references parent_tb_test
);
 
 
begin
    
for index_num in 1 .. 10000 loop
    insert into parent_tb_test
    select index_num , 'kerry' || to_char(index_num) from dual;
    
    if mod(index_num,100) = 0 then
        commit;
    end if;
end loop;
 
     commit;
    
end;
/
 
declare index_num number :=1;
begin
 
    for index_parent  in 1 .. 10000 loop
        for index_child in 1 .. 1000 loop
             insert into child_tb_test
             select index_num, index_parent, 'child' || to_char(index_child) from dual;
             
             index_num := index_num +1;
             if mod(index_child,1000) = 0 then
                 commit;
             end if;
        end loop;
    end loop;
 
    commit;
end;
/
 
 
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');
 
PL/SQL procedure successfully completed.
 
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');
 
PL/SQL procedure successfully completed.
 
SQL>

上面腳本構造了測試用的例子和數據, 那麼咱們對比看看外鍵有無索引的區別:

 

SQL> set linesize 1200
SQL> set autotrace traceonly
SQL> select p.id , p.name,c.child_name
  2  from test.parent_tb_test p
  3  inner join test.child_tb_test c on p.id = c.f_id 
  4  where p.id=1000;
 
1000 rows selected.
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 901213199
 
--------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |  1009 | 44396 |  4706  (21)| 00:00:07 |
|   1 |  NESTED LOOPS                |                   |  1009 | 44396 |  4706  (21)| 00:00:07 |
|   2 |   TABLE ACCESS BY INDEX ROWID| PARENT_TB_TEST    |     1 |    31 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN         | PK_PARENT_TB_TEST |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS FULL          | CHILD_TB_TEST     |  1009 | 13117 |  4705  (21)| 00:00:07 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("P"."ID"=1000)
   4 - filter("C"."F_ID"=1000)
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      32855  consistent gets
      32772  physical reads
          0  redo size
      29668  bytes sent via SQL*Net to client
       1218  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed
 
SQL> 

clip_image003

 

建立索引後,咱們再來看看其執行計劃,注意對比建立索引先後,執行計劃的差別,以下所示:

 

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

clip_image004

 

接下來,咱們再來看看外鍵缺失索引影響併發,以及形成死鎖的狀況,以下所示,建立表dead_lock_parent與dead_lock_foreign,二者存在主外鍵關係,分佈插入兩條測試數據:

 

SQL> create table dead_lock_parent( id number primary key, name varchar2(32));
 
Table created.
 
SQL> create table dead_lock_foreign(fid  number, fname varchar2(32), foreign key(fid) references dead_lock_parent);
 
Table created.
 
SQL> insert into dead_lock_parent values( 1, 'kerry');
 
1 row created.
 
SQL> insert into dead_lock_foreign values(1, 'kerry_fk');  
 
1 row created.
 
SQL> insert into dead_lock_parent values(2, 'jimmy');
 
1 row created.
 
SQL> insert into dead_lock_foreign values(2, 'jimmy_fk');
 
1 row created.
 
SQL> commit;
 
Commit complete.
 
SQL> 

 

1:在會話1(會話ID爲789)裏面執行下面SQL語句:

 

SQL> show user;
USER 爲 "TEST"
SQL> select * from v$mystat where rownum=1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       789          0          1
 
SQL> delete from dead_lock_foreign where fid=1;
 
已刪除 1 行。
 

 

2:在會話2(會話ID爲766)裏面執行下面SQL語句:

 

SQL> show user;
USER is "TEST"
SQL> select * from v$mystat where rownum=1;
 
       SID STATISTIC#      VALUE
---------- ---------- ----------
       766          0          1
 
SQL> delete from dead_lock_foreign where fid=2;
 
1 row deleted.

 

 

3:接着在會話1(會話ID爲789)裏執行刪除dead_lock_parent中id爲1的記錄:

SQL> delete from dead_lock_parent where id=1;

 

 

此時你會發現會話被阻塞了,咱們能夠用下面SQL查詢具體的阻塞信息。

COL MODE_HELD FOR A14;
COL LOCK_TYPE FOR A8;
COL MODE_REQUESTED FOR A10;
COL OBJECT_TYPE FOR A14;
COL OBJECT_NAME FOR A20;
SELECT LK.SID,
       DECODE(LK.TYPE,
              'TX',
              'Transaction',
              'TM',
              'DML',
              'UL',
              'PL/SQL User Lock',
              LK.TYPE) LOCK_TYPE,
       DECODE(LK.LMODE,
              0,
              'None',
              1,
              'Null',
              2,
              'Row-S (SS)',
              3,
              'Row-X (SX)',
              4,
              'Share',
              5,
              'S/Row-X (SSX)',
              6,
              'Exclusive',
              TO_CHAR(LK.LMODE)) MODE_HELD,
       DECODE(LK.REQUEST,
              0,
              'None',
              1,
              'Null',
              2,
              'Row-S (SS)',
              3,
              'Row-X (SX)',
              4,
              'Share',
              5,
              'S/Row-X (SSX)',
              6,
              'Exclusive',
              TO_CHAR(LK.REQUEST)) MODE_REQUESTED, 
       OB.OBJECT_TYPE,
       OB.OBJECT_NAME,
       LK.BLOCK,
       SE.LOCKWAIT
  FROM V$LOCK LK, DBA_OBJECTS OB, V$SESSION SE
 WHERE LK.TYPE IN ('TM', 'UL')
   AND LK.SID = SE.SID
   AND LK.ID1 = OB.OBJECT_ID(+)
 AND SE.SID IN (766,789)
 ORDER BY SID;

clip_image005

 

上面信息若是不能讓你理解,那麼能夠看看下面腳本,相信你能看得更詳細。

SQL> SELECT S.SID                             SID, 
         S.USERNAME                        USERNAME, 
         S.MACHINE                         MACHINE, 
         L.TYPE                            TYPE, 
         O.OBJECT_NAME                     OBJECT_NAME, 
         DECODE(L.LMODE, 0, 'None', 
                         1, 'Null', 
                         2, 'Row Share', 
                         3, 'Row Exlusive', 
                         4, 'Share', 
                         5, 'Sh/Row Exlusive', 
                         6, 'Exclusive')   lmode, 
    DECODE(L.REQUEST, 0, 'None', 
                           1, 'Null', 
                           2, 'Row Share', 
                           3, 'Row Exlusive', 
                           4, 'Share', 
                           5, 'Sh/Row Exlusive', 
                           6, 'Exclusive') request, 
         L.BLOCK                           BLOCK 
  FROM   V$LOCK L, 
         V$SESSION S, 
         DBA_OBJECTS O 
  WHERE  L.SID = S.SID 
         AND USERNAME != 'SYSTEM' 
         AND O.OBJECT_ID(+) = L.ID1 
         AND S.SID IN ( 766,789) 
  ORDER  BY S.SID; 
 
       SID USERNAME MACHINE        TY OBJECT_NAME          LMODE           REQUEST         BLOCK
---------- -------- -------------- -- -------------------- --------------- --------------- -----
       766 TEST     XXXX\GET253194 TX                      Exclusive       None                0
       766 TEST     XXXX\GET253194 TM DEAD_LOCK_FOREIGN    Row Exlusive    None                1
       766 TEST     XXXX\GET253194 TM DEAD_LOCK_PARENT     Row Exlusive    None                0
       789 TEST     DB-Server.loca TX                      Exclusive       None                0
                    ldomain
 
       789 TEST     DB-Server.loca TM DEAD_LOCK_PARENT     Row Exlusive    None                0
                    ldomain
 
       789 TEST     DB-Server.loca TM DEAD_LOCK_FOREIGN    Row Exlusive    Sh/Row Exlusive     0
                    ldomain    

接着在會話2裏面執行下面SQL,刪除主表中id=2的記錄

SQL> delete from dead_lock_parent where id=2;

 

你會發現會話1就會出現Deadlock

 

clip_image006

 

若是你在外鍵字段上建立索引,那麼這種狀況下的操做就不會出現死鎖。在這裏就再也不贅述。有興趣能夠測試一下.

 

外鍵建立索引建議(Foreign Key Indexing Tips)

 

雖然增長索引,可能會帶來一些額外的性能開銷(DML操做開銷增長)和磁盤空間方面的開銷,可是相比其帶來的性能改善而言,這些額外的開銷其實徹底能夠忽略。若是沒有其餘特殊狀況,建議全部的外鍵字段都加上索引。在Oracle Oracle Database 9i/10g/11g編程藝術這本書中介紹了在何時不須要對外鍵加索引. 必須知足下面三個條件:

1: 不會刪除父表中的行。

2: 不管是有意仍是無心,總之不會更新父表的惟一/主鍵字段值。

3: 不會從父表聯結到子表, 或者更通俗的講,外鍵列不支持子表的一個重要訪問路徑,並且你在謂詞中沒有使用這些外鍵累從子表中選擇數據。

 

找出未索引的外鍵

 

咱們首先能夠經過下面腳本,找到整個數據庫中那些表有主外鍵關係,並列出主外鍵約束.

 

--查看整個數據庫下擁有主外鍵關係的全部表(排除一些系統用戶)

--查看整個數據庫下擁有主外鍵關係的全部表(排除一些系統用戶)   
SELECT DC.OWNER                   AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME              AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME         AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME         AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER                   AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME              AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME =DF.R_CONSTRAINT_NAME 
       AND DC.OWNER NOT IN ( 'SYSTEM', 'SYS', 'DBSNMP', 'EXFSYS', 
                            'ORDDATA', 'CTXSYS', 'OLAPSYS', 'MDSYS', 
                            'SYSMAN' ); 

 

--查看某個Schema下擁有主外鍵關係的全部表

--查看某個Schema下擁有主外鍵關係的全部表   
SELECT DC.OWNER           AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME      AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER           AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME      AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME 
       AND DC.OWNER =UPPER('&OWNER');  

 

--查看某個具體的表是否和其它表擁有主外鍵關係

--查看某個具體的表是否和其它表擁有主外鍵關係  
SELECT DC.OWNER           AS "PARENT_TABLE_OWNER", 
       DC.TABLE_NAME      AS "PARENT_TABLE_NAME", 
       DC.CONSTRAINT_NAME AS "PRIMARY CONSTRAINT NAME", 
       DF.CONSTRAINT_NAME AS "REFERENCED CONSTRAINT NAME", 
       DF.OWNER           AS "CHILD_TABLE_OWNER", 
       DF.TABLE_NAME      AS "CHILD_TABLE_NAME" 
FROM   DBA_CONSTRAINTS DC, 
       (SELECT C.OWNER, 
               C.CONSTRAINT_NAME, 
               C.R_CONSTRAINT_NAME, 
               C.TABLE_NAME 
        FROM   DBA_CONSTRAINTS C 
        WHERE  CONSTRAINT_TYPE = 'R') DF 
WHERE  DC.CONSTRAINT_NAME = DF.R_CONSTRAINT_NAME 
       AND DC.OWNER =UPPER('&OWNER')
       AND DC.TABLE_NAME=UPPER('&TABLE_NAME');

 

接下來咱們要找出在具體的外鍵字段是否有索引,腳本以下所示:

SELECT   CON.OWNER ,
         CON.TABLE_NAME,
         CON.CONSTRAINT_NAME,
         CON.COL_LIST,
         'No Indexed' AS INDEX_STATUS
FROM
     (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME,
           MAX(DECODE(POSITION, 1,     '"' ||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 2,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 3,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 4,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 5,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 6,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 7,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 8,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 9,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 10,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST
           FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC
           WHERE DC.OWNER = CC.OWNER
           AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME
           AND DC.CONSTRAINT_TYPE = 'R'
           AND DC.OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS', 'ADMIN')
           GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME 
     ) CON
      WHERE NOT EXISTS (
        SELECT 1 FROM
                  ( SELECT TABLE_OWNER, TABLE_NAME,   
                       MAX(DECODE(COLUMN_POSITION, 1,     '"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 2,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 3,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 4,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 5,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 6,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 7,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 8,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 9,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 10,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST
                       FROM DBA_IND_COLUMNS 
                   WHERE TABLE_OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS')
                   GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL
    WHERE CON.OWNER = COL.TABLE_OWNER 
    AND CON.TABLE_NAME = COL.TABLE_NAME  
    AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ;  

 

若是是ORACLE 11g或以上版本,數據庫有分析函數LISTAGG的話,可使用下面腳本

SELECT CASE 
         WHEN B.TABLE_NAME IS NULL THEN 'NO INDEXED' 
         ELSE 'INDEXED' 
       END               AS STATUS, 
       A.TABLE_OWNER     AS TABLE_OWNER, 
       A.TABLE_NAME      AS TABLE_NAME, 
       A.CONSTRAINT_NAME AS FK_NAME, 
       A.FK_COLUMNS      AS FK_COLUMNS, 
       B.INDEX_NAME      AS INDEX_NAME, 
       B.INDEX_COLUMNS   AS INDEX_COLUMNS 
FROM   (SELECT A.OWNER                              AS TABLE_OWNER, 
               A.TABLE_NAME                         AS TABLE_NAME, 
               A.CONSTRAINT_NAME                    AS CONSTRAINT_NAME, 
               LISTAGG(A.COLUMN_NAME, ',') 
                 WITHIN GROUP (ORDER BY A.POSITION) FK_COLUMNS 
        FROM   DBA_CONS_COLUMNS A, 
               DBA_CONSTRAINTS B 
        WHERE  A.CONSTRAINT_NAME = B.CONSTRAINT_NAME 
               AND B.CONSTRAINT_TYPE = 'R' 
               AND A.OWNER = B.OWNER 
               AND A.OWNER NOT IN ( 'SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 
                                    'MDSYS' ) 
        GROUP  BY A.OWNER, 
                  A.TABLE_NAME, 
                  A.CONSTRAINT_NAME) A, 
       (SELECT TABLE_OWNER, 
               TABLE_NAME, 
               INDEX_NAME, 
               LISTAGG(C.COLUMN_NAME, ',') 
                 WITHIN GROUP (ORDER BY C.COLUMN_POSITION) INDEX_COLUMNS 
        FROM   DBA_IND_COLUMNS C 
        GROUP  BY TABLE_OWNER, 
                  TABLE_NAME, 
                  INDEX_NAME) B 
WHERE  A.TABLE_NAME = B.TABLE_NAME(+) 
       AND A.TABLE_OWNER = B.TABLE_OWNER(+) 
       AND B.INDEX_COLUMNS(+) LIKE A.FK_COLUMNS 
                                   || '%' 
ORDER  BY 1 DESC

 

自動生成建立外鍵索引的腳本

 

上面的這些腳本已經能找出那些外鍵字段已經創建或未創建索引,此時若是對外鍵字段缺乏索引的表手工建立索引的話,若是數量不少的話,那麼工做量也很是大,下面能夠用這個腳本自動生成缺失的索引

/*******************************************************************************************
--腳本功能描述:
--  對於數據庫中外鍵缺乏索引的字段,生成對應的索引(排除一些系統帳號,例如sys、system),若是外鍵索引超過十個字段
--  那麼這個腳本就不能正確的生成對應的索引,固然也不多有外鍵設置在超過10個字段的。另外索引表空
--  空間跟數據表空間相同,若有分開的話,建議在此處再作調整。
********************************************************************************************/
SELECT    'CREATE INDEX ' || OWNER || '.' || REPLACE(CONSTRAINT_NAME,'FK_','IX_') || 
        ' ON ' || OWNER || '.' || TABLE_NAME || ' (' || COL_LIST ||') TABLESPACE ' 
        || (SELECT TABLESPACE_NAME FROM DBA_TABLES WHERE OWNER= CON.OWNER AND TABLE_NAME= CON.TABLE_NAME) 
        AS  CREATE_INDEXES_ON_FOREIGN_KEY 
FROM
     (SELECT CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME,
           MAX(DECODE(POSITION, 1,     '"' ||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 2,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 3,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 4,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 5,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 6,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 7,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 8,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 9,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) ||
           MAX(DECODE(POSITION, 10,', '||'"'||
                  SUBSTR(COLUMN_NAME,1,30) ||'"',NULL)) COL_LIST
           FROM DBA_CONSTRAINTS DC, DBA_CONS_COLUMNS CC
           WHERE DC.OWNER = CC.OWNER
           AND DC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME
           AND DC.CONSTRAINT_TYPE = 'R'
           AND DC.OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS', 'ADMIN')
           GROUP BY CC.OWNER, CC.TABLE_NAME, CC.CONSTRAINT_NAME 
     ) CON
      WHERE NOT EXISTS (
        SELECT 1 FROM
                  ( SELECT TABLE_OWNER, TABLE_NAME,   
                       MAX(DECODE(COLUMN_POSITION, 1,     '"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 2,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 3,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 4,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 5,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 6,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 7,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 8,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 9,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) ||
                       MAX(DECODE(COLUMN_POSITION, 10,', '||'"'||
                              SUBSTR(COLUMN_NAME,1,30)  ||'"',NULL)) COL_LIST
                       FROM DBA_IND_COLUMNS 
                   WHERE TABLE_OWNER NOT IN ('SYS', 'SYSTEM', 'OLAPSYS', 'SYSMAN', 'MDSYS')
                   GROUP BY TABLE_OWNER, TABLE_NAME, INDEX_NAME ) COL
    WHERE CON.OWNER = COL.TABLE_OWNER 
    AND CON.TABLE_NAME = COL.TABLE_NAME  
    AND CON.COL_LIST = SUBSTR(COL.COL_LIST, 1, LENGTH(CON.COL_LIST) ) )  ; 

 

--腳本使用分析函數LISTAGG, 適用於ORACLE 11g以及以上版本,若是數據庫版本是Oracle 11g及以上,就可使用此腳本替代上面腳本。

SELECT 'CREATE INDEX ' 
              || OWNER 
              || '.' 
              || REPLACE(CONSTRAINT_NAME,'FK_','IX_') 
              || ' ON ' 
              || OWNER 
              || '.' 
              || TABLE_NAME 
              || ' (' 
              || FK_COLUMNS 
              ||') TABLESPACE ' 
              || 
       ( 
              SELECT TABLESPACE_NAME 
              FROM   DBA_TABLES 
              WHERE  OWNER= CON.OWNER 
              AND    TABLE_NAME= CON.TABLE_NAME) CREATE_INDEXES_ON_FOREIGN_KEY 
FROM   ( 
                SELECT   CC.OWNER, 
                         CC.TABLE_NAME, 
                         CC.CONSTRAINT_NAME, 
                         LISTAGG(CC.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY CC.POSITION) FK_COLUMNS
                FROM     DBA_CONS_COLUMNS CC, 
                         DBA_CONSTRAINTS DC 
                WHERE    CC.CONSTRAINT_NAME = DC.CONSTRAINT_NAME 
                AND      DC.CONSTRAINT_TYPE = 'R' 
                AND      CC.OWNER = DC.OWNER 
                AND      DC.OWNER NOT IN ( 'SYS', 
                                          'SYSTEM', 
                                          'OLAPSYS', 
                                          'SYSMAN', 
                                          'MDSYS', 
                                          'ADMIN' ) 
                GROUP BY CC.OWNER, 
                         CC.TABLE_NAME, 
                         CC.CONSTRAINT_NAME) CON 
  WHERE NOT EXISTS 
       ( 
              SELECT 1 
              FROM   ( 
                              SELECT   TABLE_OWNER, 
                                       TABLE_NAME, 
                                       INDEX_NAME,
                                       LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_POSITION) FK_COLUMNS
                              FROM     DBA_IND_COLUMNS 
                              WHERE    INDEX_OWNER NOT IN ( 'SYS', 
                                                           'SYSTEM', 
                                                           'OLAPSYS', 
                                                           'SYSMAN', 
                                                           'MDSYS', 
                                                           'ADMIN' ) 
                                                         
                              GROUP BY TABLE_OWNER, 
                                       TABLE_NAME ,INDEX_NAME) COL 
              WHERE  CON.OWNER = COL.TABLE_OWNER 
              AND    CON.TABLE_NAME = COL.TABLE_NAME 
              AND    CON.FK_COLUMNS = SUBSTR(COL.FK_COLUMNS, 1, LENGTH(CON.FK_COLUMNS)) ) 
              ORDER BY 1;

 

參考資料:

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

相關文章
相關標籤/搜索