一次DeleteInsert引发的Mysql死锁
近日遇到一個(gè)比較奇怪的deadlock錯(cuò)誤, 錯(cuò)誤詳情:
Deadlock found when trying to get lock; try restarting transaction; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException...
跟蹤代碼后最終定位到一段業(yè)務(wù)邏輯:
delete from A where no = $no; insert into A(no, value) values($no, "value");印象中mysql一直是使用行級(jí)鎖, 為什么此處在并發(fā)時(shí)會(huì)發(fā)生死鎖呢? 唯一的解釋是mysql在這邊鎖住的不只一行數(shù)據(jù).
簡單搜索之后, 發(fā)現(xiàn)mysql的鎖分為三種(按照鎖定的行數(shù)劃分):
1.record lock:記錄鎖,也就是僅僅鎖著單獨(dú)的一行
2.gap lock:區(qū)間鎖,僅僅鎖住一個(gè)區(qū)間(注意這里的區(qū)間都是開區(qū)間,也就 是不包括邊界值,至于為什么這么定義?innodb官方定義的)
3.next-key lock:record lock+gap lock,所以next-key lock也就半開半閉區(qū)間,且是下界開,上界閉。(為什么這么定義?innodb官方定義的)
由于此處是在明確指定了no=XX的情況下拋出了死鎖異常, 并且no建立的是普通索引, 所以此處mysql使用的應(yīng)該是next-key lock(查看何種情況下使用何種鎖?https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html).
下面來舉個(gè)手冊(cè)上的例子看看next-key lock是如何上鎖的。假如一個(gè)索引的行有10,11,13,20
那么可能的next-key lock的包括:
(無窮小, 10]
(10,11]
(11,13]
(13,20]
(20, 無窮大)
下面分析何種情況下會(huì)發(fā)生死鎖.
結(jié)合業(yè)務(wù)邏輯, 執(zhí)行新增操作時(shí)也會(huì)執(zhí)行一樣的邏輯, 先進(jìn)行delete.
例如,現(xiàn)在表student中有四條數(shù)據(jù):
Image 1
現(xiàn)在要新增一條數(shù)據(jù), no = 21, 這時(shí)候會(huì)先進(jìn)行delete, 線程會(huì)鎖住(20, 無窮大)這塊區(qū)間, 加入這個(gè)時(shí)候另一個(gè)線程正在新增另一條數(shù)據(jù) no = 22, 線程也會(huì)鎖住(20, 無窮大)這塊區(qū)間就會(huì)發(fā)生死鎖.
下面看具體實(shí)驗(yàn):
創(chuàng)建一張表student.
插入數(shù)據(jù):
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
執(zhí)行兩個(gè)事務(wù):
session 1:
session 2:
begin; delete from student where no = 22;此處解釋一下, 此時(shí),session 1和session 2都會(huì)對(duì)區(qū)間(20, 無窮大)加鎖, 而區(qū)間鎖只是用來防止其他事務(wù)在區(qū)間中插入數(shù)據(jù),區(qū)間x鎖 與區(qū)間S鎖效果是一樣的(只要不是插入操作), 因此兩個(gè)session都會(huì)持有鎖.
參考: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.
繼續(xù)執(zhí)行:
session 1:
此時(shí)session 1阻塞(因?yàn)閟ession 2持有區(qū)間鎖), 如圖:
Image 3
session 2:
INSERT INTO student (no,name) VALUES(22, "Zhoubing");此時(shí)session 2死鎖(因?yàn)閟ession 1持有區(qū)間鎖), 如圖:
Image 4
.
總結(jié), delete之后進(jìn)行insert有可能發(fā)生死鎖, 因?yàn)閐elete可能會(huì)持有區(qū)間鎖, 而區(qū)間鎖是可重入的(只要不是插入數(shù)據(jù)).
解決方案:
將事務(wù)隔離級(jí)別將為read commit.
?
參考資料:
1.Next-Key Locks
2.Locks Set by Different SQL Statements in InnoDB
總結(jié)
以上是生活随笔為你收集整理的一次DeleteInsert引发的Mysql死锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL Innodb表导致死锁日志情
- 下一篇: mysql insert锁机制