本節以第 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
隔離等級效果良好,因為同一交易中的每個一致性讀取都會從其自己的最新快照中讀取。如果其他方法都無效,請使用資料表層級鎖定來序列化您的交易。使用交易式資料表(例如
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 的資料表層級鎖定,必須使用逾時方法來解決死鎖。