什么是死鎖
MySQL的死鎖指的是兩個(gè)事務(wù)互相等待的場(chǎng)景,這種循環(huán)等待理論上不會(huì)有盡頭。
比如事務(wù)A持有行1的鎖,事務(wù)B持有行2的鎖,
然后事務(wù)A試圖獲取行2的鎖,事務(wù)B試圖獲取行1的鎖,
這樣事務(wù)A要等待事務(wù)B釋放行2的鎖,事務(wù)B要等待事務(wù)A釋放行1的鎖,
兩個(gè)事務(wù)互相等待,誰也提交不了。這種情況下MySQL會(huì)選擇中斷并回滾其中一個(gè)事務(wù),使得另一個(gè)事務(wù)可以提交。MySQL會(huì)記錄死鎖的日志。
死鎖案列
MySQL版本:Server version: 8.0.27 MySQL Community Server - GPL
測(cè)試表
CREATE TABLE `record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`biz_id` bigint(20) NOT NULL DEFAULT 1,
`vid` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
index idx_biz_id(`biz_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化數(shù)據(jù)
insert into record(biz_id,vid) values(10,1231);
insert into record(biz_id,vid) values(12,8716);
insert into record(biz_id,vid) values(14,2948);
insert into record(biz_id,vid) values(16,7463);
+----+--------+------+
| id | biz_id | vid |
+----+--------+------+
| 1 | 10 | 1231 |
| 2 | 12 | 8716 |
| 3 | 14 | 2948 |
| 4 | 16 | 7463 |
+----+--------+------+
先刪再插以此實(shí)現(xiàn)insertOrUpdate
步驟 | 事務(wù)A | 事務(wù)B |
---|---|---|
1 | set autocommit=0; |
|
2 | delete from record where id=18; |
|
3 | set autocommit=0; |
|
4 | delete from record where id=18; |
|
5 |
insert into record(biz_id,vid) values(18,1231) blocked
|
|
6 |
insert into record(biz_id,vid) values(18,1231) ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
|
7 | Query OK, 1 row affected (9.30 sec) |
查看日志
show engine innodb status
發(fā)現(xiàn)死鎖日志
LATEST DETECTED DEADLOCK
------------------------
2022-03-03 16:41:21 0x70000da29000
*** (1) TRANSACTION:
// 事務(wù)ID,活躍時(shí)間,事務(wù)正在進(jìn)行的操作
TRANSACTION 1897, ACTIVE 79 sec inserting
//修改1個(gè)表,鎖1行數(shù)據(jù)
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
//線程ID和queryId
MySQL thread id 24, OS thread handle 123145545482240, query id 166 localhost root update
// 執(zhí)行的SQL
insert into record(biz_id,vid) values(18,1231)
*** (1) HOLDS THE LOCK(S):
//持有的鎖信息
RECORD LOCKS space id 6 page no 4 n bits 72 index PRIMARY of table `test`.`record` trx id 1897 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
//等待的鎖
RECORD LOCKS space id 6 page no 4 n bits 72 index PRIMARY of table `test`.`record` trx id 1897 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 1898, ACTIVE 49 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 20, OS thread handle 123145546547200, query id 167 localhost root update
insert into record(biz_id,vid) values(18,1231)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 6 page no 4 n bits 72 index PRIMARY of table `test`.`record` trx id 1898 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6 page no 4 n bits 72 index PRIMARY of table `test`.`record` trx id 1898 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
//死鎖處理結(jié)果
*** WE ROLL BACK TRANSACTION (2)
MySQL的8種鎖
1 行鎖(Record Locks)
行鎖是作用在索引上的。
2 間隙鎖(Gap Locks)
間隙鎖是鎖住一個(gè)區(qū)間的鎖。
這個(gè)區(qū)間是一個(gè)開區(qū)間,范圍是從某個(gè)存在的值向左直到比他小的第一個(gè)存在的值,所以間隙鎖包含的內(nèi)容就是在查詢范圍內(nèi),而又不存在的數(shù)據(jù)區(qū)間。
比如有id分別是1,10,20,要修改id<15的數(shù)據(jù),那么生成的間隙鎖有以下這些:(-∞,1),(1,10),(10,20),此時(shí)若有其他事務(wù)想要插入id=11的數(shù)據(jù),則需要等待。
間隙鎖是不互斥的。
作用是防止其他事務(wù)在區(qū)間內(nèi)添加記錄,而本事務(wù)可以在區(qū)間內(nèi)添加記錄,從而防止幻讀。
在可重復(fù)讀這種隔離級(jí)別下會(huì)啟用間隙鎖,而在讀未提交和讀已提交兩種隔離級(jí)別下,即使使用select ... in share mode或select ... for update,也不會(huì)有間隙鎖,無法防止幻讀。
3 臨鍵鎖(Next-key Locks)
臨鍵鎖=間隙鎖+行鎖,于是臨鍵鎖的區(qū)域是一個(gè)左開右閉的區(qū)間。
隔離級(jí)別是可重復(fù)讀時(shí),select ... in share mode或select ... for update會(huì)使用臨鍵鎖,防止幻讀。普通select語句是快照讀,不能防止幻讀。
4 共享鎖/排他鎖(Shared and Exclusive Locks)
共享鎖和排它鎖都是行鎖。共享鎖用于事務(wù)并發(fā)讀取,比如select ... in share mode。排它鎖用于事務(wù)并發(fā)更新或刪除。比如select ... for update
5 意向共享鎖/意向排他鎖(Intention Shared and Exclusive Locks)
意向共享鎖和意向排他鎖都是表級(jí)鎖。
官方文檔中說,事務(wù)獲得共享鎖前要先獲得意向共享鎖,獲得排它鎖前要先獲得意向排它鎖。
意向排它鎖互相之間是兼容的。
6 插入意向鎖(Insert Intention Locks)
插入意向鎖鎖的是一個(gè)點(diǎn),是一種特殊的間隙鎖,用于并發(fā)插入。
插入意向鎖和間隙鎖互斥。插入意向鎖互相不互斥。
7 自增鎖(Auto-inc Locks)
自增鎖用于事務(wù)中插入自增字段。5.1版本前是表鎖,5.1及以后版本是互斥輕量鎖。
自增所相關(guān)的變量有:
auto_increment_offset,初始值
auto_increment_increment,每次增加的數(shù)量
innodb_autoinc_lock_mode,自增鎖模式
其中:
innodb_autoinc_lock_mode=0,傳統(tǒng)方式,每次都產(chǎn)生表鎖。此為5.1版本前的默認(rèn)配置。
innodb_autoinc_lock_mode=1,連續(xù)方式。產(chǎn)生輕量鎖,申請(qǐng)到自增鎖就將鎖釋放,simple insert會(huì)獲得批量的鎖,保證連續(xù)插入。此為5.2版本后的默認(rèn)配置。
innodb_autoinc_lock_mode=2,交錯(cuò)鎖定方式。不鎖表,并發(fā)速度最快。但最終產(chǎn)生的序列號(hào)和執(zhí)行的先后順序可能不一致,也可能斷裂。
鎖組合
首先我們要知道對(duì)于MySQL有兩種常規(guī)鎖模式
LOCK_S(讀鎖,共享鎖)
LOCK_X(寫鎖,排它鎖)
最容易理解的鎖模式,讀加共享鎖,寫加排它鎖.
有如下幾種鎖的屬性
LOCK_REC_NOT_GAP (鎖記錄)
LOCK_GAP (鎖記錄前的GAP)
LOCK_ORDINARY (同時(shí)鎖記錄+記錄前的GAP 。傳說中的Next Key鎖)
LOCK_INSERT_INTENTION(插入意向鎖,其實(shí)是特殊的GAP鎖)
鎖的屬性可以與鎖模式任意組合。例如.
lock->type_mode 可以是Lock_X 或者Lock_S
locks gap before rec 表示為gap鎖:lock->type_mode & LOCK_GAP
locks rec but not gap 表示為記錄鎖,非gap鎖:lock->type_mode &
LOCK_REC_NOT_GAP
insert intention 表示為插入意向鎖:lock->type_mode & LOCK_INSERT_INTENTION
waiting 表示鎖等待:lock->type_mode & LOCK_WAIT
鎖之間兼容性
X 排他鎖
S 共享鎖
IX 意向排他鎖
IS 意向共享鎖
X | IX | S | IS | |
---|---|---|---|---|
X | ||||
IX | 兼容 | 兼容 | ||
S | 兼容 | 兼容 | ||
IS | 兼容 | 兼容 | 兼容 |
死鎖解鎖
InnoDB存儲(chǔ)引擎會(huì)選擇回滾undo量最小的事務(wù)