在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
如上所示,當外鍵字段沒有索引時,父表與子表關聯時,子表會進行全表掃描,下面,我在外鍵字段建立索引後,就能避免子表表掃描了。oop
CREATE INDEX SCOTT.IX_DEPTNO ON SCOTT.EMP ("DEPTNO") TABLESPACE USERS;
固然這兩個表的數據量實在是太少了,性能上差異不大,當數據量增加上去後,這個性能差別就會比較明顯了。以下例子所示,咱們構造一個數據量相對較大的父表與子表:性能
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>
建立索引後,咱們再來看看其執行計劃,注意對比建立索引先後,執行計劃的差別,以下所示:
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;
接下來,咱們再來看看外鍵缺失索引影響併發,以及形成死鎖的狀況,以下所示,建立表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;
上面信息若是不能讓你理解,那麼能夠看看下面腳本,相信你能看得更詳細。
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
若是你在外鍵字段上建立索引,那麼這種狀況下的操做就不會出現死鎖。在這裏就再也不贅述。有興趣能夠測試一下.
外鍵建立索引建議(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;
參考資料: