MySQL 8.0 新特性之死鎖檢測(cè)控制
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫(kù)管理與開發(fā)經(jīng)驗(yàn),目前在一家全球性的金融公司從事數(shù)據(jù)庫(kù)架構(gòu)設(shè)計(jì)。CSDN學(xué)院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
在 MySQL 中,如果兩個(gè)不同的事務(wù)在執(zhí)行時(shí),互相持有了對(duì)方所需的鎖,此時(shí)由于它們都在等待某個(gè)資源,永遠(yuǎn)不會(huì)釋放自己獲得的鎖,因此就會(huì)產(chǎn)生死鎖(deadlock)。
以下是一個(gè)產(chǎn)生死鎖的示例。首先,在客戶端 A 中創(chuàng)建一個(gè)表 t,它只有一行數(shù)據(jù)。然后開始一個(gè)事務(wù),并且通過(guò)共享查詢模式獲取該行數(shù)據(jù)上的 S 鎖。
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 FOR SHARE;
+------+
| i |
+------+
| 1 |
+------+
接下來(lái),在客戶端 B 中開始另一個(gè)事務(wù),并且嘗試刪除該行數(shù)據(jù):
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
刪除操作需要獲得一個(gè)排他鎖(X)。由于客戶端 A 已經(jīng)獲得了一個(gè) S 鎖,客戶端 B 的鎖請(qǐng)求需要進(jìn)入鎖的等待隊(duì)列,因此客戶端 B 被阻塞。
最后,在客戶端 A 中也嘗試刪除該行數(shù)據(jù):
mysql> DELETE FROM t WHERE i = 1;
Query OK, 1 row affected (0.00 sec)
同時(shí),在客戶端 B 返回以下錯(cuò)誤信息:
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
此時(shí)產(chǎn)生了死鎖,因?yàn)榭蛻舳?A 需要獲得一個(gè) X 鎖才能刪除該行數(shù)據(jù)。然而,客戶端 A 無(wú)法獲得該鎖,因?yàn)榭蛻舳?B 已經(jīng)請(qǐng)求了一個(gè) X 鎖,并且等待客戶端 A 釋放該數(shù)據(jù)行上的 S 鎖??蛻舳?A 上的 S 鎖也無(wú)法升級(jí)為 X 鎖,因?yàn)榭蛻舳?B 的 X 鎖請(qǐng)求優(yōu)先級(jí)更高。結(jié)果就是,InnoDB 將會(huì)在某個(gè)客戶端產(chǎn)生錯(cuò)誤,并且釋放它所獲取的鎖。
至此,另一個(gè)客戶端(A)能夠獲得請(qǐng)求的鎖,并且刪除該數(shù)據(jù)行。
通過(guò)以上示例可以看出,當(dāng)啟用了死鎖檢測(cè)時(shí)(默認(rèn)設(shè)置),InnoDB 自動(dòng)執(zhí)行事務(wù)的死鎖檢測(cè),并且回滾一個(gè)或多個(gè)事務(wù)以解決死鎖。InnoDB 嘗試回滾更小的事務(wù),事務(wù)的大小由它所插入、更新或者刪除的數(shù)據(jù)行數(shù)決定。
在 MySQL 8.0 中,增加了一個(gè)新的動(dòng)態(tài)變量:innodb_deadlock_detect,可以用于控制 InnoDB 是否執(zhí)行死鎖檢測(cè)。該參數(shù)的默認(rèn)值為 ON,即打開死鎖檢測(cè)。
mysql> show variables like 'innodb_deadlock_detect';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_deadlock_detect | ON |
+------------------------+-------+
1 row in set (0.00 sec)
對(duì)于高并發(fā)的系統(tǒng),當(dāng)大量線程等待同一個(gè)鎖時(shí),死鎖檢測(cè)可能會(huì)導(dǎo)致性能的下降。此時(shí),如果禁用死鎖檢測(cè),而改為依靠參數(shù) innodb_lock_wait_timeout 執(zhí)行發(fā)生死鎖時(shí)的事務(wù)回滾可能會(huì)更加高效。
接下來(lái),先在客戶端 A 中關(guān)閉死鎖檢測(cè):
mysql> set global innodb_deadlock_detect=off;
Query OK, 0 rows affected (0.04 sec)
重復(fù)上文中的示例,在客戶端 A 中執(zhí)行以下操作:
mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50 |
+--------------------------+-------+
1 row in set (0.00 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 FOR SHARE;
+------+
| i |
+------+
| 1 |
+------+
在客戶端 B 執(zhí)行刪除操作:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
客戶端 B 等待 X 鎖,仍然被阻塞。再回到客戶端 A,嘗試刪除該行數(shù)據(jù):
mysql> DELETE FROM t WHERE i = 1;
Query OK, 1 row affected (37.87 sec)
此時(shí)客戶端 B 不會(huì)提示死鎖錯(cuò)誤,而是等待 50 s (innodb_lock_wait_timeout 設(shè)置值)后提示等待超時(shí):
mysql> DELETE FROM t WHERE i = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
同時(shí)客戶端 A 在此之后成功刪除數(shù)據(jù),可以看到刪除操作的時(shí)間為 37.87 s 。這個(gè)時(shí)間依取決于最后在客戶端 A 中輸入刪除語(yǔ)句的時(shí)間。
通常來(lái)說(shuō),應(yīng)該啟用死鎖檢測(cè),并且在應(yīng)用程序中盡量避免產(chǎn)生死鎖,同時(shí)對(duì)死鎖進(jìn)行相應(yīng)的處理,例如重新開始事務(wù)。
只有在確認(rèn)死鎖檢測(cè)影響了系統(tǒng)的性能,并且禁用死鎖檢測(cè)不會(huì)帶來(lái)負(fù)面影響時(shí),可以嘗試關(guān)閉 innodb_deadlock_detect 選項(xiàng)。另外,如果禁用了 InnoDB 死鎖檢測(cè),需要調(diào)整參數(shù) innodb_lock_wait_timeout 的值,以滿足實(shí)際的需求。
官方文檔:MySQL 8.0 Reference Manual