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.

參考資料:
1.Next-Key Locks
2.Locks Set by Different SQL Statements in InnoDB

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • MySQL技術內幕:InnoDB存儲引擎(第2版) 姜承堯 第1章 MySQL體系結構和存儲引擎 >> 在上述例子...
    沉默劍士閱讀 7,460評論 0 16
  • 當一個系統訪問量上來的時候,不只是數據庫性能瓶頸問題了,數據庫數據安全也會浮現,這時候合理使用數據庫鎖機制就顯得異...
    初來的雨天閱讀 3,611評論 0 22
  • 前言 數據庫鎖定機制是數據庫為了保證數據的一致性而使各種共享資源在并發訪問時變的有序的一種規則。MySQL數據庫的...
    Justlearn閱讀 1,701評論 0 4
  • 1背景1 1.1MVCC:Snapshot Read vs Current Read2 1.2Cluster In...
    簡小鹿奔跑ing閱讀 4,190評論 1 50
  • 基本上大多數人都是以自我為中心的去闡述一個事情,應該是一個觀點。這個認知看來也是我的個人觀點。角度,認知,邏輯思維...
    木木梵音yoga閱讀 743評論 0 4