[轉]一次Delete&Insert引發的Mysql死鎖


近日遇到一個比較奇怪的deadlock錯誤, 錯誤詳情:

Deadlock found when trying to get lock; try restarting transaction; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException...

跟蹤代碼后最終定位到一段業務邏輯:

delete from A where no = $no;
insert into A(no, value) values($no, "value");

印象中mysql一直是使用行級鎖, 為什么此處在並發時會發生死鎖呢? 唯一的解釋是mysql在這邊鎖住的不只一行數據.

簡單搜索之后, 發現mysql的鎖分為三種(按照鎖定的行數划分):
1.record lock:記錄鎖,也就是僅僅鎖着單獨的一行
2.gap lock:區間鎖,僅僅鎖住一個區間(注意這里的區間都是開區間,也就 是不包括邊界值,至於為什么這么定義?innodb官方定義的)
3.next-key lock:record lock+gap lock,所以next-key lock也就半開半閉區間,且是下界開,上界閉。(為什么這么定義?innodb官方定義的)

由於此處是在明確指定了no=XX的情況下拋出了死鎖異常, 並且no建立的是普通索引, 所以此處mysql使用的應該是next-key lock(查看何種情況下使用何種鎖 https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html).

下面來舉個手冊上的例子看看next-key lock是如何上鎖的。假如一個索引的行有10,11,13,20
那么可能的next-key lock的包括:
(無窮小, 10]
(10,11]
(11,13]
(13,20]
(20, 無窮大)

下面分析何種情況下會發生死鎖.
結合業務邏輯, 執行新增操作時也會執行一樣的邏輯, 先進行delete.
例如,現在表student中有四條數據:


Image 1

現在要新增一條數據, no = 21, 這時候會先進行delete, 線程會鎖住(20, 無窮大)這塊區間, 加入這個時候另一個線程正在新增另一條數據 no = 22, 線程也會鎖住(20, 無窮大)這塊區間就會發生死鎖.

下面看具體實驗:
創建一張表student.

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `no` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_no` (`no`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=latin1

插入數據:

INSERT INTO student (no,name) VALUES(10, "Jim");
INSERT INTO student (no,name) VALUES(11, "Kimi");
INSERT INTO student (no,name) VALUES(13, "Tom");
INSERT INTO student (no,name) VALUES(20, "Mike");

Image 2

執行兩個事務:
session 1:

begin;
delete from student where no = 21;

session 2:

begin;
delete from student where no = 22;

此處解釋一下, 此時,session 1和session 2都會對區間(20, 無窮大)加鎖, 而區間鎖只是用來防止其他事務在區間中插入數據,區間x鎖 與區間S鎖效果是一樣的(只要不是插入操作), 因此兩個session都會持有鎖.

參考:https://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html
Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. Thus, a gap X-lock has the same effect as a gap S-lock.

繼續執行:
session 1:

INSERT INTO student (no,name) VALUES(21, "Zhoubing");

此時session 1阻塞(因為session 2持有區間鎖), 如圖:


Image 3

session 2:

INSERT INTO student (no,name) VALUES(22, "Zhoubing");

此時session 2死鎖(因為session 1持有區間鎖), 如圖:


Image 4

.

總結, delete之后進行insert有可能發生死鎖, 因為delete可能會持有區間鎖, 而區間鎖是可重入的(只要不是插入數據).

解決方案:
將事務隔離級別將為read commit.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM