關(guān)于Oracle數(shù)據(jù)庫的死鎖(轉(zhuǎn)書摘)
6.2.6 死鎖
如果你有兩個會話,每個會話都持有另一個會話想要的資源,此時就會出現(xiàn)死鎖(deadlock)。例如,如果我的數(shù)據(jù)庫中有兩個表A和B,每個表中都只有一行,就可以很容易地展示什么是死鎖。我要做的只是打開兩個會話(例如,兩個SQL*Plus會話)。在會話A中更新表A,并在會話B中更新表B。現(xiàn)在,如果我想在會話B中更新表A,就會阻塞。會話A已經(jīng)鎖定了這一行。這不是死鎖;只是阻塞而已。我還沒有遇到過死鎖,因為會話A還有機會提交或回滾,這樣會話B就能繼續(xù)。
如果我再回到會話A,試圖更新表B,這就會導(dǎo)致一個死鎖。要在這兩個會話中選擇一個作為“犧牲品”,讓它的語句回滾。例如,會話B中對表A的更新可能回滾,得到以下錯誤:

Oracle認為死鎖很少見,而且由于如此少見,所以每次出現(xiàn)死鎖時它都會在服務(wù)器上創(chuàng)建一個跟蹤文件。這個跟蹤文件的內(nèi)容如下:

顯然,Oracle認為這些應(yīng)用死鎖是應(yīng)用自己導(dǎo)致的錯誤,而且在大多數(shù)情況下,Oracle的這種看法都是正確的。不同于許多其他的RDBMS,Oracle中極少出現(xiàn)死鎖,甚至可以認為幾乎不存在。通常情況下,必須人為地提供條件才會產(chǎn)生死鎖。
根據(jù)我的經(jīng)驗,導(dǎo)致死鎖的頭號原因是外鍵未加索引(第二號原因是表上的位圖索引遭到并發(fā)更新,這個內(nèi)容將在第11章討論)。在以下兩種情況下,Oracle在修改父表后會對子表加一個全表鎖:
q 如果更新了父表的主鍵(倘若遵循關(guān)系數(shù)據(jù)庫的原則,即主鍵應(yīng)當是不可變的,這種情況就很少見),由于外鍵上沒有索引,所以子表會被鎖住。
q 如果刪除了父表中的一行,整個子表也會被鎖住(由于外鍵上沒有索引)。
在Oracle9i及以上版本中,這些全表鎖都是短期的,這意味著它們僅在DML操作期間存在,而不是在整個事務(wù)期間都存在。即便如此,這些全表鎖還是可能(而且確實會)導(dǎo)致很嚴重的鎖定問題。下面說明第二點,如果用以下命令建立了兩個表:



然后執(zhí)行以下語句:
到目前為止,還沒有什么問題。但是如果再到另一個會話中,試圖刪除第一條父記錄:
此時就會發(fā)現(xiàn),這個會話立即被阻塞了。它在執(zhí)行刪除之前試圖對表C加一個全表鎖。現(xiàn)在,別的會話都不能對C中的任何行執(zhí)行DELETE、INSERT或UPDATE(已經(jīng)開始的會話可以繼續(xù),但是新會話將無法修改C)。
更新主鍵值也會發(fā)生這種阻塞。因為在關(guān)系數(shù)據(jù)庫中,更新主鍵是一個很大的禁忌,所以更新在這方面一般沒有什么問題。在我看來,如果開發(fā)人員使用能生成SQL的工具,而且這些工具會更新每一列,而不論最終用戶是否確實修改了那些列,此時更新主鍵就會成為一個嚴重的問題。例如,假設(shè)我們使用了Oracle Forms,并為表創(chuàng)建了一個默認布局。默認情況下,Oracle Forms會生成一個更新,對我們選擇要顯示的表中的每一列進行修改。如果在DEPT表中建立一個默認布局,包括3個字段,只要我們修改了DEPT表中的任何列,Oracle Forms都會執(zhí)行以下命令:
在這種情況下,如果EMP表有DEPT的一個外鍵,而且在EMP表的DEPTNO列上沒有任何索引,那么更新DEPT時整個EMP表都會被鎖定。如果你使用了能生成SQL的工具,就一定要當心這一點。即便主鍵值沒有改變,執(zhí)行前面的SQL語句后,子表EMP也會被鎖定。如果使用Oracle Forms,解決方案是把這個表的UPDATE CHANGED COLUMNS ONLY屬性設(shè)置為YES。這樣一來,Oracle Forms會生成一條UPDATE語句,其中只包含修改過的列(而不包括主鍵)。
刪除父表中的一行可能導(dǎo)致子表被鎖住,由此產(chǎn)生的問題更多。我已經(jīng)說過,如果刪除表P中的一行,那么在DML操作期間,子表C就會鎖定,這樣能避免事務(wù)期間對C執(zhí)行其他更新(當然,這有一個前提,即沒有人在修改C;如果確實已經(jīng)有人在修改C,刪除會等待)。此時就會出現(xiàn)阻塞和死鎖問題。通過鎖定整個表C,數(shù)據(jù)庫的并發(fā)性就會大幅下降,以至于沒有人能夠修改C中的任何內(nèi)容。另外,出現(xiàn)死鎖的可能性則增加了,因為我的會話現(xiàn)在“擁有”大量數(shù)據(jù),直到提交時才會交出。其他會話因為C而阻塞的可能性也更大;只要會話試圖修改C就會被阻塞。因此,我開始注意到,數(shù)據(jù)庫中大量會話被阻塞,這些會話持有另外一些資源的鎖。實際上,如果其中任何阻塞的會話鎖住了我的會話需要的資源,就會出現(xiàn)一個死鎖。在這種情況下,造成死鎖的原因是:我的會話不允許別人訪問超出其所需的更多資源(在這里就是一個表中的所有行)。如果有人抱怨說數(shù)據(jù)庫中存在死鎖,我會讓他們運行一個腳本,查看是不是存在未加索引的外鍵,而且在99%的情況下都會發(fā)現(xiàn)表中確實存在這個問題。只需對外鍵加索引,死鎖(以及大量其他的競爭問題)都會煙消云散。下面的例子展示了如何使用這個腳本來找出表C中未加索引的外鍵:

這個腳本將處理外鍵約束,其中最多可以有8列(如果你的外鍵有更多的列,可能就得重新考慮一下你的設(shè)計了)。首先,它在前面的查詢中建立一個名為CONS的內(nèi)聯(lián)視圖(inline view)。這個內(nèi)聯(lián)視圖將約束中適當?shù)牧忻麖男修D(zhuǎn)置到列,其結(jié)果是每個約束有一行,最多有8列,這些列分別取值為約束中的列名。另外,這個視圖中還有一個列COL_CNT,其中包含外鍵約束本身的列數(shù)。對于這個內(nèi)聯(lián)視圖中返回的每一行,我們要執(zhí)行一個關(guān)聯(lián)子查詢(correlated subquery),檢查當前所處理表上的所有索引。它會統(tǒng)計出索引中與外鍵約束中的列相匹配的列數(shù),然后按索引名分組。這樣,就能生成一組數(shù),每個數(shù)都是該表某個索引中匹配列的總計。如果原來的COL_CNT大于所有這些數(shù),那么表中就沒有支持這個約束的索引。如果COL_CNT小于所有這些數(shù),就至少有一個索引支持這個約束。注意,這里使用了NVL2函數(shù),我們用這個函數(shù)把列名列表“粘到”一個用逗號分隔的列表中。這個函數(shù)有3個參數(shù):A、B和C。如果參數(shù)A非空,則返回B;否則返回參數(shù)C。這個查詢有一個前提,假設(shè)約束的所有者也是表和索引的所有者。如果另一位用戶對表加索引,或者表在另一個模式中(這兩種情況都很少見),就不能正確地工作。
所以,這個腳本展示出,表C在列X上有一個外鍵,但是沒有索引。通過對X加索引,就可以完全消除這個鎖定問題。除了全表鎖外,在以下情況下,未加索引的外鍵也可能帶來問題:
q 如果有ON DELETE CASCADE,而且沒有對子表加索引:例如,EMP是DEPT的子表,DELETE DEPTNO = 10應(yīng)該CASCADE(級聯(lián))至EMP。如果EMP中的DEPTNO沒有索引,那么刪除DEPT表中的每一行時都會對EMP做一個全表掃描。這個全表掃描可能是不必要的,而且如果從父表刪除多行,父表中每刪除一行就要掃描一次子表。
q 從父表查詢子表:再次考慮EMP/DEPT例子。利用DEPTNO查詢EMP表是相當常見的。如果頻繁地運行以下查詢(例如,生成一個報告),你會發(fā)現(xiàn)沒有索引會使查詢速度變慢:
n select * from dept, emp
n where emp.deptno = dept.deptno and dept.deptno = :X;
那么,什么時候不需要對外鍵加索引呢?答案是,一般來說,當滿足以下條件時不需要加索引:q 沒有從父表刪除行。
q 沒有更新父表的惟一鍵/主鍵值(當心工具有時會無意地更新主鍵!)。
q 沒有從父表聯(lián)結(jié)子表(如DEPT聯(lián)結(jié)到EMP)。
如果滿足上述全部3個條件,那你完全可以跳過索引,不需要對外鍵加索引。如果滿足以上的某個條件,就要當心加索引的后果。這是一種少有的情況,即Oracle“過分地鎖定了”數(shù)據(jù)。
身邊越來越多的人開始使用Mac,經(jīng)常被問道Mac的使用問題和技術(shù)問題。遂決定,每天發(fā)布一則#Mac技巧#。同時發(fā)布于微博和微信公眾賬號“Mac技巧”,微信號sagacity-mac,有微信賬號的童鞋掃描下圖片或搜索微信號即可

浙公網(wǎng)安備 33010602011771號