遭遇刪除數據耗費時間超長的問題,後臺Execution Plan跟蹤截圖如下:
對應外鍵建立索引後:
以下是關於外鍵列上是否需要索引 的文章轉載:
其實這個問題應該算是老生常談了。這兩天看concept看到這裏,於是就在說說這個問題。
外鍵列上缺少索引會帶來兩個問題,限制併發性、影響性能。而這兩個問題中的任意一個都可能會造成嚴重性能問題。
無論是Oracle的官方文檔,還是在Tom的書中都說明了兩種情況下可以忽略外鍵上的索引。其實我認爲不需要那麼麻煩,與增加一個索引所帶來的性能開銷和磁盤空間開銷相比,確實索引可能引發的問題要嚴重得多。因此,我會選擇在所有的外鍵列上添加索引,雖然可能導致創建了部分多餘的索引,但是這樣相除了外鍵約束由於確實索引所帶來的性能問題和併發性問題。
如果外鍵列上缺少索引,從主表關聯子表的查詢就只能對子表選擇全表掃描的查詢,這是顯而易見的問題:
SQL> CREATE TABLE T_P (ID NUMBER, NAME VARCHAR2(30));
表已創建。
SQL> ALTER TABLE T_P ADD PRIMARY KEY (ID);
表已更改。
SQL> CREATE TABLE T_C (ID NUMBER, FID NUMBER, NAME VARCHAR2(30));
表已創建。
SQL> ALTER TABLE T_C ADD CONSTRAINT FK_T_C
2 FOREIGN KEY (FID)
3 REFERENCES T_P (ID);
表已更改。
SQL> INSERT INTO T_P SELECT ROWNUM, TABLE_NAME FROM ALL_TABLES;
已創建884行。
SQL> INSERT INTO T_C SELECT ROWNUM, MOD(ROWNUM, 884) + 1, OBJECT_NAME
2 FROM ALL_OBJECTS;
已創建30339行。
SQL> COMMIT;
提交完成。
SQL> SELECT A.ID, A.NAME, B.NAME
2 FROM T_P A, T_C B
3 WHERE A.ID = B.FID
4 AND A.ID = 880;
ID NAME NAME
---------- ------------------------------ ------------------------------
880 T_COMPRESS /eb2b6b5_Options1
880 T_COMPRESS DATE
880 T_COMPRESS DEF$_SCHEDULE
880 T_COMPRESS GV_$SESSION_EVENT
.
.
.
880 T_COMPRESS sun/io/ByteToCharCp1251
880 T_COMPRESS /5ba3839f_DirStateFactoryResul
880 T_COMPRESS USER_INDEXTYPES
已選擇34行。
執行計劃
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE
1 0 MERGE JOIN
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T_P'
3 2 INDEX (UNIQUE SCAN) OF 'SYS_C002964' (UNIQUE)
4 1 FILTER
5 4 TABLE ACCESS (FULL) OF 'T_C'
統計信息
----------------------------------------------------------
0 recursive calls
0 db block gets
190 consistent gets
0 physical reads
0 redo size
1829 bytes sent via SQL*Net to client
394 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
34 rows processed
由於缺少索引,上面的這個關聯查詢只能採用MERGE JOIN,而如果聯立了外鍵列上的索引:
SQL> CREATE INDEX IND_T_C_FID ON T_C (FID);
索引已創建。
SQL> SELECT A.ID, A.NAME, B.NAME
2 FROM T_P A, T_C B
3 WHERE A.ID = B.FID
4 AND A.ID = 880;
ID NAME NAME
---------- ------------------------------ ------------------------------
880 T_COMPRESS /e1538703_EntryInfoImpl
880 T_COMPRESS /7b832daf_ObjectStreamClassCom
880 T_COMPRESS java/awt/peer/ScrollbarPeer
880 T_COMPRESS /1982bd95_PermissionsEnumerato
.
.
.
880 T_COMPRESS /9ebda46b_GetInterface
880 T_COMPRESS /c71f85e7_DefaultPopupFactory
880 T_COMPRESS /7b549d81_DataFormatException
已選擇34行。
執行計劃
----------------------------------------------------------
0 SELECT STATEMENT ptimizer=CHOOSE
1 0 NESTED LOOPS
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T_P'
3 2 INDEX (UNIQUE SCAN) OF 'SYS_C002964' (UNIQUE)
4 1 TABLE ACCESS (BY INDEX ROWID) OF 'T_C'
5 4 INDEX (RANGE SCAN) OF 'IND_T_C_FID' (NON-UNIQUE)
統計信息
----------------------------------------------------------
0 recursive calls
0 db block gets
42 consistent gets
1 physical reads
0 redo size
1829 bytes sent via SQL*Net to client
394 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
34 rows processed
上面是影響性能的例子,下面看看外鍵列索引對併發性的影響:
SQL> SET AUTOT OFF
SQL> SELECT * FROM T_P WHERE ID < 5;
ID NAME
---------- ------------------------------
1 SEG$
2 CLU$
3 OBJ$
4 FILE$
SQL> SELECT * FROM T_C WHERE ID < 5;
ID FID NAME
---------- ---------- ------------------------------
1 2 /1005bd30_LnkdConstant
2 3 /10076b23_OraCustomDatumClosur
3 4 /10297c91_SAXAttrList
4 5 /103a2e73_DefaultEditorKitEndP
下面在另一個會話中刪除子表的一條記錄:
SQL> SET SQLP 'SQL2> '
SQL2> DELETE T_C WHERE ID = 2;
已刪除 1 行。
刪除了一條爲2的子表級聯,其對應的主表記錄ID爲3,下面嘗試在第一個會話新增一條ID爲1000的記錄,然後刪除這條記錄:
SQL> INSERT INTO T_P VALUES (1000, 'A');
已創建 1 行。
SQL> DELETE T_P WHERE ID = 1000;
已刪除 1 行。
SQL> ROLLBACK;
回退已完成。
可以看到,並沒有發生鎖表的情況,這是因爲子表外鍵列上有索引,刪除主表的記錄時,只會鎖定子表參考主表的對應記錄。
會話二回滾:
SQL2> ROLLBACK;
回退已完成。
下面刪除外鍵索引:
SQL> DROP INDEX IND_T_C_FID;
索引已刪除。
重複剛纔的操作,在另一個會話執行刪除操作:
SQL2> DELETE T_C WHERE ID = 2;
已刪除 1 行。
在會話一重複插入和刪除操作:
SQL> INSERT INTO T_P VALUES (1000, 'A');
已創建 1 行。
SQL> DELETE T_P WHERE ID = 1000;
這時會話被鎖住,因爲缺少了外鍵索引後,主表刪除或更新記錄會導致子表整個表被鎖,而這會導致嚴重的系統併發問題。
SQL2> ROLLBACK;
回退已完成。
會話2回滾後,會話1的刪除操作纔可以繼續執行:
已刪除 1 行。
SQL>
可能有些人會認爲,系統中不存在刪除而不會導致這個問題,其實不僅是刪除,主鍵列的更新同樣可以導致這個問題。
而且這種更新可能是工具幫你自動完成的,因爲很多工具會自動生成SQL語句,而在這種生成的SQL語句中,UPDATE的列是表中的所有列,所以即使主鍵的值沒有發生變化,但是仍然是被更新了:
SQL2> DELETE T_C WHERE ID = 2;
已刪除 1 行。
還是刪除這條ID爲2的子表記錄,下面在主表執行一個更新操作:
SQL> UPDATE T_P SET ID = 500 WHERE ID = 500;
可以看到,不管值是否發生了變化,只要主鍵列被更新,就會導致操作被鎖定。
顯而易見,不加索引的外鍵列會造成嚴重的性能問題,所以除非你有十分的把握,否則還是在外鍵列上添加索引吧。