2009-10-05 88 views
12

我們有一個基於Web的應用程序。在應用程序中有時間限制的數據庫操作(INSERT和UPDATE)需要更多時間才能完成,因此此特定流程已更改爲Java線程,因此不會等待(阻止)完成完整的數據庫操作。INSERT語句中的死鎖錯誤

我的問題是,如果超過1個用戶遇到這一特定流程,我面臨PostgreSQL可以拋出以下錯誤:

org.postgresql.util.PSQLException: ERROR: deadlock detected 
    Detail: Process 13560 waits for ShareLock on transaction 3147316424; blocked by process 13566. 
Process 13566 waits for ShareLock on transaction 3147316408; blocked by process 13560. 

上述錯誤在INSERT語句中始終拋出。

附加信息: 1)我有本表中定義的PRIMARY KEY。 2)本表中有FOREIGN KEY引用。 3)單獨的數據庫連接被傳遞給每個Java線程。

技術 Web服務器:Tomcat的v6.0.10 的Java V1.6.0 Servlet的 數據庫:在PostgreSQL v8.2.3 連接管理:pgpool II

+0

其他信息對診斷問題將有所幫助。您能否提供特定的外鍵約束,關於表模式的一些基本信息以及導致死鎖的實際SQL語句? – 2009-10-05 14:38:24

回答

0

你的問題,也許是INSERT命令試圖鎖定一個或兩個索引,並且索引鎖定在另一個踏板上。

一個常見的錯誤是在每個線程上以不同順序鎖定資源。檢查訂單並嘗試在所有線程中以相同順序鎖定資源。

+1

PostgreSQL在常規語句中不鎖定索引,只在表中的行中鎖定索引。爲了鎖定索引,你必須特別修改或維護它。 – 2009-10-06 05:48:18

5

死鎖解釋
概括地說,正在發生的事情是,一個特定的SQL語句(INSERT或其他)正在等待另一份聲明,以解除對數據庫的特定部分的鎖,才能進入。在這個鎖被釋放之前,第一個SQL語句稱爲「語句A」將不允許自己訪問這部分數據庫來完成它的工作(=常規鎖定情形)。但是......聲明A還對的另一個部分數據庫進行了鎖定,以確保沒有其他數據庫用戶訪問(用於讀取或修改/刪除,取決於鎖的類型)。現在......第二個SQL語句本身需要訪問由語句A的鎖標記的數據部分。這是一個DEAD LOCK:兩個語句都會相互等待,無限次地進行。

補救的辦法...

這就需要了解這些不同的線程在運行,並期待在那裏的具體的SQL語句,如果有一種方法可以之一:

 
a) removing some of the locks, or changing their types. 
    For example, maybe the whole table is locked, whereby only a given row, or 
    a page thereof would be necessary. 
b) preventing multiple of these queries to be submitted at a given time. 
    This would be done by way of semaphores/locks (aka MUTEX) at the level of the 
    multi-threading logic. 

當心如果沒有正確實現,「b)」方法可能會將死鎖從SQL內移到程序/線程邏輯中。關鍵是要創建一個互斥體,首先由任何將要運行這些容易死鎖的查詢的線程獲取。

21

解決死鎖的一種方法是有一個等待隨機間隔的重試機制並嘗試再次運行事務。隨機時間間隔是必要的,以便碰撞事務不會持續相互碰撞,造成所謂的活鎖 - 這甚至更難以調試。事實上,大多數複雜的應用程序在需要處理事務序列化失敗時遲早都會需要這樣的重試機制。

當然,如果你能夠確定死鎖的原因,它通常是更好的消除它,否則回來咬你。對於幾乎所有的情況,即使在死鎖情況很少的情況下,吞吐量和編碼開銷的一點點以獲得確定性順序的鎖或獲得更粗粒度的鎖也是值得的,以避免偶然的大延遲命中和突然的性能懸崖擴展併發性時。

當您持續獲得兩個INSERT語句死鎖時,它很可能是唯一的索引插入順序問題。嘗試例如在兩個PSQL命令窗口中的以下內容:

Thread A   | Thread B 
BEGIN;    | BEGIN; 
        | INSERT uniq=1; 
INSERT uniq=2;  | 
        | INSERT uniq=2; 
        | block waiting for thread A to commit or rollback, to 
        | see if this is an unique key error. 
INSERT uniq=1;  | 
    blocks waiting | 
    for thread B, | 
    DEADLOCK  | 
        V  

通常最好的行動當然,解決這個是要弄清楚守衛所有此類交易的父對象。大多數應用程序都有一個或兩個主要實體,例如用戶或帳戶,因此很適合用於此目的。然後,您需要的是每個事務都通過SELECT ... FOR UPDATE獲取它接觸的主要實體上的鎖。或者,如果觸及幾個,每次都會以相同的順序鎖定它們(按主鍵排序是個不錯的選擇)。

10

PostgreSQL在這裏的功能在Explicit Locking的文檔中有介紹。 「Deadlocks」部分中的示例顯示了您可能正在做的事情。您可能沒有料到的部分是,當您更新某些內容時,該行將獲得對該行的鎖定,直到涉及的事務結束。如果你有多個客戶端同時更新一個以上的東西,你將不可避免地導致死鎖,除非你想方設法阻止它們。

如果你有多個事物取出像UPDATE這樣的隱式鎖,你應該將整個序列封裝在BEGIN/COMMIT事務塊中,並且確保你對它們獲得的鎖的順序是一致的(即使是隱含的鎖也是如此更新抓取)到處。如果你需要更新表A和表B中的某些內容,並且應用程序的一部分執行A然後B,而另一部分執行B然後A,那麼你有一天會陷入僵局。對同一張表進行的兩次更新同樣註定會失敗,除非您可以對客戶端之間的可重複執行的二者進行強制排序。一旦您擁有更新的記錄集並且始終首先抓取「較低」記錄,則通過主鍵進行排序是一種常用策略。

你的INSERT不太可能被歸咎於這裏,除非你像螞蟻已經描述的那樣違反了主鍵,否則這些更難陷入僵局。

你不想做的是嘗試和重複你的應用程序中的鎖定,這將變成一個巨大的可伸縮性和可靠性混亂(並可能仍然會導致數據庫死鎖)。如果您無法在標準數據庫鎖定方法的範圍內解決此問題,請考慮使用諮詢鎖定工具或明確的LOCK TABLE來強制執行您所需的操作。這將爲你節省一個痛苦的編碼世界,試圖將所有的鎖推到客戶端。如果您對一個表有多個更新並且無法強制執行它們發生的順序,那麼除了在執行它們時鎖定整個表,您別無選擇;這是唯一不會引起僵局潛力的途徑。