本節以 第 17.7.5.2 節,「死鎖偵測」中有關死鎖的概念性資訊為基礎。它說明如何組織資料庫操作以最小化死鎖,以及應用程式中所需的後續錯誤處理。
死鎖是交易式資料庫中的經典問題,但除非死鎖頻繁到您完全無法執行某些交易,否則它們並不危險。通常,您必須編寫應用程式,以便它們隨時準備好在交易因死鎖而回滾時重新發出交易。
InnoDB
使用自動列級鎖定。即使在僅插入或刪除單列的交易中,也可能會發生死鎖。這是因為這些操作並非真正的「「原子」」;它們會自動設定所插入或刪除之列的 (可能多個) 索引記錄上的鎖定。
您可以使用下列技術來處理死鎖並降低其發生的可能性
隨時發出
SHOW ENGINE INNODB STATUS
以判斷最近死鎖的原因。這可以協助您調整應用程式以避免死鎖。如果頻繁的死鎖警告引起關注,請啟用
innodb_print_all_deadlocks
變數來收集更廣泛的除錯資訊。有關每個死鎖 (而不僅僅是最近的死鎖) 的資訊會記錄在 MySQL 錯誤記錄中。在您完成除錯時,停用此選項。如果交易因死鎖而失敗,請務必準備好重新發出交易。死鎖並不危險。只要再試一次即可。
保持交易的範圍小且持續時間短,以使其較不容易發生衝突。
在對一組相關變更進行變更之後立即提交交易,以使其較不容易發生衝突。特別是,不要讓互動式 mysql 工作階段在未提交交易的情況下開啟太久。
如果您使用鎖定讀取 (
SELECT ... FOR UPDATE
或SELECT ... FOR SHARE
),請嘗試使用較低的隔離層級,例如READ COMMITTED
。當在交易中修改多個資料表,或在同一個資料表中修改不同的列集合時,請每次以一致的順序執行這些操作。然後,交易會形成定義完善的佇列,而不會發生死鎖。例如,在應用程式中將資料庫操作組織到函式中,或呼叫預存常式,而不是在不同的位置編碼多個類似的
INSERT
、UPDATE
和DELETE
陳述式序列。在您的資料表上加入適當的索引,以便您的查詢掃描較少的索引記錄並設定較少的鎖定。 使用
EXPLAIN SELECT
來判斷 MySQL 伺服器認為哪些索引最適合您的查詢。減少鎖定的使用。如果您可以允許
SELECT
從舊的快照傳回資料,請不要在其中加入FOR UPDATE
或FOR SHARE
子句。在這裡使用READ COMMITTED
隔離級別是個好主意,因為同一個交易中的每個一致性讀取都會從它自己全新的快照中讀取。如果其他方法都無效,請使用資料表層級的鎖定來序列化您的交易。將
LOCK TABLES
與交易型資料表(例如InnoDB
資料表)一起使用的正確方法是,使用SET autocommit = 0
(而不是START TRANSACTION
)開始交易,然後是LOCK TABLES
,並且在您明確提交交易之前不要呼叫UNLOCK TABLES
。例如,如果您需要寫入資料表t1
並從資料表t2
讀取,您可以執行以下操作SET autocommit=0; LOCK TABLES t1 WRITE, t2 READ, ...; ... do something with tables t1 and t2 here ... COMMIT; UNLOCK TABLES;
資料表層級的鎖定可以防止對資料表的同時更新,從而避免死鎖,但會犧牲繁忙系統的反應速度。
另一種序列化交易的方法是建立一個輔助的「信號量」資料表,該資料表只包含一行。讓每個交易在存取其他資料表之前更新該行。這樣,所有交易都會以序列方式發生。請注意,
InnoDB
的即時死鎖偵測演算法在這種情況下也有效,因為序列化鎖定是資料列層級的鎖定。對於 MySQL 資料表層級鎖定,必須使用逾時方法來解決死鎖。