徹底理解mysql innodb的死鎖

死鎖只發生在併發的情況,也就是說你的程序是串行的是不可能發生死鎖。通常表現爲兩個事務都在等待某個資源可用而無法繼續進行,因爲每個事務都持有另一個事務需要的鎖,而它們都不會釋放所持有的鎖。爲此,InnoDB引擎有一個後臺的鎖監控線程,它負責查看可能的死鎖問題,並自動告知用戶。可以通過 innodb_deadlock_detect 配置選項禁用死鎖檢測,innodb還提供了innodb_lock_wait_timeout 配置在死鎖情況下回滾事務。

死鎖的可能性不受隔離級別的影響,因爲隔離級別會更改讀取操作的行爲,而死鎖是由於寫入操作而發生的。當事務鎖定多個表中的行時(通過諸如:UPDATE 或 SELECT ... FOR UPDATE),可能會發生死鎖,但順序相反。當這些語句鎖定索引記錄的範圍和間隙時(參閱之前分享的行鎖、間隙鎖及Next-KEY),也可能發生死鎖。

死鎖示例

話不多說直接上代碼看死鎖如何發生的:

CREATE TABLE t (i INT) ENGINE = InnoDB;
INSERT INTO t (i) VALUES(1);
--客戶端A
START TRANSACTION;
SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;

--客戶端B
START TRANSACTION;
DELETE FROM t WHERE i = 1;

客戶端B報錯:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

因爲刪除操作需要X鎖,無法獲取該鎖,因爲它與客戶端A持有的S鎖不兼容

死鎖檢測和回滾

死鎖檢測( deadlock detection)默認是啓用的,InnoDB自動檢測事務死鎖並回滾一個或多個事務以打破死鎖。當InnoDB執行事務的完全回滾時,該事務設置的所有鎖都將被釋放

另外,如果頻繁發生的死鎖,也可以通過啓用 innodb_print_all_deadlocks配置將有關所有死鎖的信息打印到mysqld錯誤日誌中。每個死鎖的信息,不僅僅是最新的死鎖,都記錄在mysql錯誤日誌中,完成調試後禁用此選項。

什麼情況下死鎖檢測不會生效?

如果涉及mysql的 LOCK TABLES語句設置的表鎖或非innodb存儲引擎設置的鎖,innodb無法檢測到死鎖。但可以通過設置innodb_lock_wait_timeout系統變量的值來解決這個情況。

如何選擇死鎖犧牲品呢?

我們知道:SQL Server會把它認爲取消或回滾代價最小的連接作爲默認的死鎖犧牲品。而InnoDB嘗試選擇要回滾的小事務,其中事務的大小由插入、更新或刪除的行數決定。

爲何要禁用死鎖檢測?

在高併發系統上,當許多線程等待相同的鎖時,死鎖檢測可能會導致速度減慢。有時,當死鎖發生時,禁用死鎖檢測並依靠 innodb_lock_wait_timeout 設置進行事務回滾可能更有效。可以使用 innodb_deadlock_detect配置選項禁用死鎖檢測。

如何最小化和處理死鎖

您可以使用以下技術來處理死鎖並降低發生死鎖的可能性:

1. 隨時發出show engine innodb status命令以確定最近死鎖的原因。如果InnoDB監視器輸出的(LATEST DETECTED DEADLOCK )節點包含一條消息:“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION,” ,這表示等待列表上的事務數已達到200個限制。超過200個事務的等待列表將被視爲死鎖,試圖檢查等待列表的事務將回滾。如果鎖定線程必須查看等待列表上事務擁有的超過1000000個鎖,則也可能發生相同的錯誤。

2. 啓用innodb_print_all_deadlocks配置,前面講過

3. 保持事務的小規模和短持續時間,以減少它們發生衝突的可能性

4. 所有的相關更改請立即提交事務,以減少它們發生衝突的可能性。尤其是:不要讓交互式MySQL會話在未提交事務的情況下長時間處於打開狀態

5. 使用較低的隔離級別,如果使用鎖定讀取(SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),建議使用讀提交的級別

6. 一致的順序執行,在一個事務中修改多個表或同一個表中的不同行集時,每次都以一致的順序執行這些操作,然後事務形成定義良好的隊列,並且不會死鎖

7. 索引,選擇最適合查詢列的建索引,保證的查詢可以掃描更少的索引記錄,從而設置更少的鎖

8. 使用更少的鎖,如果您可以允許select從舊快照返回數據,請不要使用FOR UPDATE 或 LOCK IN SHARE MODE子句。這裏使用read-committed隔離級別是很好的,因爲同一事務中的每個一致讀取都是從自己的新快照讀取的。

9. 使用表級鎖序列化事務(表級鎖防止對錶進行併發更新,以犧牲繁忙系統的響應能力來避免死鎖),lock tables 是用來加表級鎖,但是是MySQL的server層來加這把鎖的。當innodb_table_locks = 1 (the default) 以及 autocommit = 0的時候,innodb能夠感知表鎖,同時server層瞭解到innodb已經加了row-level locks。否則,innodb將無法自動檢測到死鎖,同時server無法確定是否有行級鎖,導致當其他會話佔用行級鎖的時候還能獲得表鎖。

SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;

10. 序列化事務,創建一個只包含一行的輔助“信號量”表。在訪問其他表之前,讓每個事務更新該行。這樣,所有事務都以串行方式發生。注意,在這種情況下,InnoDB即時死鎖檢測算法也可以工作,因爲序列化鎖是一個行級鎖。對於MySQL表級鎖,必須使用超時方法來解決死鎖。

總結

爲了減少死鎖的可能性,請使用事務而不是鎖表語句。使插入或更新數據的事務足夠小,以至於它們不會長時間保持打開狀態。當不同的事務更新多個表或大範圍的行時,在每個事務中請使用相同的操作順序(SELECT ... FOR UPDATE)。在select中使用的列上創建索引(SELECT ... FOR UPDATE 和 UPDATE ... WHERE)。

參閱資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章