什么是死锁
MySQL的死锁指的是两个事务互相等待的场景,这种循环等待理论上不会有尽头。
比如事务A持有行1的锁,事务B持有行2的锁,
然后事务A试图获取行2的锁,事务B试图获取行1的锁,
这样事务A要等待事务B释放行2的锁,事务B要等待事务A释放行1的锁,
两个事务互相等待,谁也提交不了。这种情况下MySQL会选择中断并回滚其中一个事务,使得另一个事务可以提交。MySQL会记录死锁的日志。
死锁案列
MySQL版本:Server version: 8.0.27 MySQL Community Server - GPL
测试表
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;
初始化数据
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 |
+----+--------+------+
先删再插以此实现insertOrUpdate
步骤 | 事务A | 事务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
发现死锁日志
LATEST DETECTED DEADLOCK
------------------------
2022-03-03 16:41:21 0x70000da29000
*** (1) TRANSACTION:
// 事务ID,活跃时间,事务正在进行的操作
TRANSACTION 1897, ACTIVE 79 sec inserting
//修改1个表,锁1行数据
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
// 执行的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;;
//死锁处理结果
*** WE ROLL BACK TRANSACTION (2)
MySQL的8种锁
1 行锁(Record Locks)
行锁是作用在索引上的。
2 间隙锁(Gap Locks)
间隙锁是锁住一个区间的锁。
这个区间是一个开区间,范围是从某个存在的值向左直到比他小的第一个存在的值,所以间隙锁包含的内容就是在查询范围内,而又不存在的数据区间。
比如有id分别是1,10,20,要修改id<15的数据,那么生成的间隙锁有以下这些:(-∞,1),(1,10),(10,20),此时若有其他事务想要插入id=11的数据,则需要等待。
间隙锁是不互斥的。
作用是防止其他事务在区间内添加记录,而本事务可以在区间内添加记录,从而防止幻读。
在可重复读这种隔离级别下会启用间隙锁,而在读未提交和读已提交两种隔离级别下,即使使用select ... in share mode或select ... for update,也不会有间隙锁,无法防止幻读。
3 临键锁(Next-key Locks)
临键锁=间隙锁+行锁,于是临键锁的区域是一个左开右闭的区间。
隔离级别是可重复读时,select ... in share mode或select ... for update会使用临键锁,防止幻读。普通select语句是快照读,不能防止幻读。
4 共享锁/排他锁(Shared and Exclusive Locks)
共享锁和排它锁都是行锁。共享锁用于事务并发读取,比如select ... in share mode。排它锁用于事务并发更新或删除。比如select ... for update
5 意向共享锁/意向排他锁(Intention Shared and Exclusive Locks)
意向共享锁和意向排他锁都是表级锁。
官方文档中说,事务获得共享锁前要先获得意向共享锁,获得排它锁前要先获得意向排它锁。
意向排它锁互相之间是兼容的。
6 插入意向锁(Insert Intention Locks)
插入意向锁锁的是一个点,是一种特殊的间隙锁,用于并发插入。
插入意向锁和间隙锁互斥。插入意向锁互相不互斥。
7 自增锁(Auto-inc Locks)
自增锁用于事务中插入自增字段。5.1版本前是表锁,5.1及以后版本是互斥轻量锁。
自增所相关的变量有:
auto_increment_offset,初始值
auto_increment_increment,每次增加的数量
innodb_autoinc_lock_mode,自增锁模式
其中:
innodb_autoinc_lock_mode=0,传统方式,每次都产生表锁。此为5.1版本前的默认配置。
innodb_autoinc_lock_mode=1,连续方式。产生轻量锁,申请到自增锁就将锁释放,simple insert会获得批量的锁,保证连续插入。此为5.2版本后的默认配置。
innodb_autoinc_lock_mode=2,交错锁定方式。不锁表,并发速度最快。但最终产生的序列号和执行的先后顺序可能不一致,也可能断裂。
锁组合
首先我们要知道对于MySQL有两种常规锁模式
LOCK_S(读锁,共享锁)
LOCK_X(写锁,排它锁)
最容易理解的锁模式,读加共享锁,写加排它锁.
有如下几种锁的属性
LOCK_REC_NOT_GAP (锁记录)
LOCK_GAP (锁记录前的GAP)
LOCK_ORDINARY (同时锁记录+记录前的GAP 。传说中的Next Key锁)
LOCK_INSERT_INTENTION(插入意向锁,其实是特殊的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存储引擎会选择回滚undo量最小的事务