MySQL 锁
1、锁的分类
- 按互斥性划分
- 共享锁(S锁)
- 排他锁(X锁)
- 共享排他锁(SX锁)
- 按锁粒度划分
- 全局锁:对整个mysql实例加锁,加锁后所有表只能读,不能改
- 表级锁
- 元数据锁(MDL)
- 意向锁
- 自增锁(Auto-Inc)
- 行级锁
- 记录锁(Record)
- 间隙锁(Gap)
- 临键锁(Next-key)
- 插入意向锁(insert intention lock)
- 按加锁思想划分
- 乐观锁
- 悲观锁
- 按加锁方式划分
- 显式锁:手动加锁
- 隐式锁: 事务下自动加的锁
2、什么时候释放锁
MySQL绝大数时候在事务结束时释放锁,也有例外
3、共享锁、排他锁
-
共享锁:(S锁)shared lock
- 行级S锁
select ... from 表名 lock in share mode 显式锁
- 表级S锁
lock tables 表名 read
-
排他锁:(X锁)exclusive lock
- 行级X锁
select ... from 表名 for update 显式锁
除了手动显式加锁以外,update、delete也会触发行级X锁- 表级X锁
lock tables 表名 write 行级S、X锁兼容性 |兼容性| S | X | |---|---|---| |S|兼容|不兼容| |X|不兼容|不兼容|
-
共享排他锁(SX锁) MySQL5.7引入的新式锁,主要为了解决SMO(Structure modification opertion 索引页分裂、索引页删除等)带来的问题;此锁主要加在B+ tree上
| 兼容性 | S | X | SX |
|---|---|---|---|
| S | 兼容 | 不兼容 | 兼容 |
| X | 不兼容 | 不兼容 | 不兼容 |
| SX | 兼容 | 不兼容 | 兼容 |
3、MySQL全局锁
针对于整个mysql实例加锁,加锁之后,实例下所有表只能读,表数据以及表定义的修改均被禁止;一般用于数据库备份;
加锁 flush tables with read lock
释放锁 unlock tables
4、MySQL表级锁
4.1、 意向锁
先说结论,属于表级别锁;意向锁的引入主要是为了可以快速判断加表级别S、X锁时,表中的记录是否已有对应的行级锁,从而避免遍历整张表,提升性能。
加表级别S锁,需要判断表中的行记录是否有X锁,如果有则加表级S锁失败;同样加表级别X锁,需要判断表中的行记录是否有S锁、X锁,如果有加表级X锁失败;引入意向锁的概率后,在表中行记录加行级S锁时,同时会增加表级别IS锁,标记表中存在行级S锁;加行级X锁时,同样也会添加表级别IX锁,这样当添加表级别S锁,如果看到有IX锁,就明白无法获取到表级别S锁,需要阻塞等待;同理加表级别X锁时,如果看到IS、IX锁,也无法加锁成功,需要阻塞等待;避免遍历表中所有行记录。
表级别锁IS、S、IX、X的兼容性说明
| 兼容性 | IS | IX | S | X |
|---|---|---|---|---|
| IS | 兼容 | 兼容 | 兼容 | 不兼容 |
| IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
| S | 兼容 | 不兼容 | 兼容 | 不兼容 |
| X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
4.2、 元数据锁(MDL锁)
MDL锁的引入主要为了解决一张表的DML和DDL操作一致性的问题;以student为例
create table student(
id not null,
name varchar(10),
primary key(id)
);
目前只有一条记录
| id | name |
|---|---|
| 1 | 貂蝉 |
| 序号 | trx 100 | trx 200 |
|---|---|---|
| ① | begin; | |
| ② | begin; | |
| ③ | select * from student where id = 1; | |
| ④ | alter table drop column name; | |
| ⑤ | commit; | |
| ⑥ | select * from student where id = 1; | |
| ⑦ | commit; |
在RR级别下,③语句执行后,结果为 id=1,name=貂蝉;⑥语句后,结果为id=1;两次的结果不一致,这样就会触发不可重复读问题;
为了解决类似问题,引入了MDL锁,规定:
- 执行DML语句,会在表上加MDL读锁(MDL S锁)
- 执行DDL语句,会在表上加MDL写锁(MDL X锁) 上诉示例,③语句执行时,会先对student表加MDL读锁,再执行④语句时此时会对student尝试加MDL写锁,因为trx 100尚未结束,所以student表上MDL读锁仍在,从而会阻塞④语句获得MDL写锁,直至trx 100事务结束释放MDL读锁,才能继续执行。
4.3、 自增锁 (AUTO-INC锁)
自增锁顾名思义,专门为auto_increment的自增主键准备,用来控制自增主键的分配。
自增锁有两种模式:
- 采用AUTO-INC锁,执行insert插入时,①在表级别加上AUTO-INC锁->②为auto_increment修饰的自增列分配自增序列值->③执行insert插入->④释放表级别的AUTO-INC锁,至此其他insert语句才可以拿到AUTO-INC锁继续执行否则阻塞等待;
- 轻量锁,执行insert插入时,①在表级别加上轻量锁->②为auto_increment修饰的自增列分配自增序列值->③释放表级别轻量锁->④执行insert插入。 两者的差别在于轻量锁在获得自增序列值之后就可以释放,所以效率更高。 MySQL中通过innodb_autoinc_lock_mode来控制使用哪种方式: |innodb_autoinc_lock_mode|含义| |---|---| |0|传统模式,采用AUTO-INC锁| |1|连续模式,AUTO-INC锁、轻量锁混用, mysql8.0之前默认| |2|交错模式,全部使用轻量锁,MySQL8.0之后默认|
- insert-like:所有的插入语句,例如 insert、replace、insert ... select、replace ... select、load data等
- simple-insert:插入前就可以明确新增行数的语句,例如 insert、replace,不包括insert into on duplicate key update
- bulk-insert:插入前无法明确新增行数的语句,例如 insert ... select、replace ... select
- mixed-insert:混合插入 例如 insert into 表名(col1, col2) values(1, 'a'), (null, 'b');
- 传统模式 该模式下,所有插入语句,都会加AUTO-INC锁;优点:可以确保auto_increment递增分配,且不会浪费;缺点:并发insert效率较低
- 连续模式 该模式下,如果是simple-insert,采用轻量级锁;如果是bulk insert,采用AUTO-INC锁;
- 交错模式
该模式下,所有插入语句,都采用轻量级锁;(如果时bulk insert这种无法提前确认插入记录行数的语句,就只能一条一条去争抢轻量级锁)
此模式下最大的弊端:如果bin-log的“binlog-formar”选择的是statement,这种基于语句的复制同步方式,在在主从复制时,可能会导致同一记录行所分配的自增主键不一致。
我的个人理解,因为轻量级锁,只会锁住auto_increment的生成,从库在依据bin-log回放SQL,并不能完美复现主库当时主键分配顺序。
trx 1: insert into student select * from student_copy limit 10;
trx 2: insert into student values(xxx);
trx1先执行,执行过程中trx2开始执行,因为只会锁自增ID分配这一步,极大概率trx2会争抢到轻量级锁,形成如下顺序
自增主键 事务 1 trx1 2 trx1 3 trx2 4 trx1 事务提交时,trx2先于trx1结束;最终回放时,trx1先执行,trx2后执行,那么从库的主键顺序就会跟主库不同。
5、MySQL行级锁
5.1、记录锁(record lock)
顾名思义,锁住一条记录, 不同条件区分S, X;
死锁日志分析关键字:rec but not gap, 例如lock_mode X lock rec but not gap
MySQL的锁均是加在索引上,如果没有主键或者索引,那么锁会加在隐藏自增列DB_ROW_ID上
5.2、 间隙锁(gap lock)
间隙锁引入的目的:为了解决幻读问题。以student表为例
create table student(
id int not null,
name varchar(10) not null,
primary key(id)
);
| id | name |
|---|---|
| 1 | 貂蝉 |
| 2 | 蔡文姬 |
| 7 | 甄姬 |
如果想解决幻读问题,单凭记录锁已经显的力不从心,record lock无论加在id=2或者id=7,都无法阻止2-7中间插入新记录,所以就诞生了间隙锁, 锁住(4, 7)之间的间隙。(前后开区间),上诉示例,间隙锁加在id=7的聚簇索引上
死锁分析关键字:lock_mode X locks gap before rec
5.3、 临键锁(next-key lock)
临键锁相当于记录锁和间隙锁的合体(前开后闭)
死锁分析关键字:lock_mode X locks
5.4、 插入意向锁(insert intention lock)
插入意向锁,也是一种间隙锁形式的意向锁,当insert时,会检查当前插入的下一条记录上是否存在间隙锁或者临键锁,如果存在则insert语句会被阻塞等待,同时会生成一个插入意向锁。
横向为持有锁,纵向为请求获得的锁
| 兼容性 | 记录锁 | 间隙锁 | 临键锁 | 插入意向锁 |
|---|---|---|---|---|
| 记录锁 | 冲突 | 兼容 | 冲突 | 兼容 |
| 间隙锁 | 兼容 | 兼容 | 兼容 | 兼容 |
| 临键锁 | 冲突 | 兼容 | 冲突 | 兼容 |
| 插入意向锁 | 兼容 | 冲突 | 冲突 | 兼容 |
6、乐观锁
乐观锁:或者叫无锁模式,利用CAS的思想;举个例子:
update stock = n, version = version + 1 where id = xx and version = version;
update order status = B where id = xx and status = A;
上诉两种都可以认为乐观锁的使用
7、悲观锁
悲观锁:一句话就是干啥先上锁
8、锁是怎么加上去的
以student表为例
create table student(
id int not null,
name varchar(10) not null,
country varchar(10) not null,
primary key(id),
key idx_name(name)
);
表中数据
| id | name | country |
|---|---|---|
| 1 | b白起 | 秦 |
| 3 | g关羽 | 蜀 |
| 5 | l李牧 | 赵 |
| 7 | c曹操 | 魏 |
| 10 | d邓艾 | 魏 |
8.1、RU/RC级
8.1.1、主键
8.1.1.1、精确
- select ... lock in share mode/select ... for update
select * from student where id = 3 lock in share mode;因为通过主键查询,直接在id=3的聚簇索引上加S锁即可。select ... for update 加锁过程与上诉相似,唯一差别就是加X记录锁
- update
- 不涉及二级索引列更新 加锁过程与select ... for update一致,只会对聚簇索引记录加锁;
- 涉及二级索引列更新
update student set name = 'g关羽123' where id = 3;
与select ... for update过程相似,差别在于先对定义聚簇索引加锁,在对涉及索引列加锁。
- delete 与update涉及二级索引加锁过程一致,先对聚簇索引加锁,再对关联的二级索引加锁;
8.1.1.2、 范围
- select ... lock in share mode/select ... for update
select * from student where id <= 5;
- 从聚簇索引根部开始遍历,找到符合查询条件的第一条记录(id=1的记录),对该记录加S记录锁;
- 判断该记录是否符合索引下推中的条件;因为id <= 5,不符合索引下推条件,且走的是聚簇索引。
- 引擎层判断下是否符合范围查询的边界条件(MySQL规定聚簇索引中的记录需要判断是否符合范围查询边界条件),如果符合返回给server层继续处理,否则释放掉记录上加的锁,同时返回server层一个查询结束的标志;
- server层收到引擎层提供的信息,如果是查询结束标记,那就结束查询;否则在server层中继续判断记录是否符合非索引下推的剩余查询条件,如果符合条件返回记录给client端,否则释放该记录上加的锁;
- 依据该记录顺着单向链表取出下一条记录,对记录同样加S记录锁,然后依次执行2、3、4、5步骤。
依据上诉查询过程,上诉语句在边界值的下一个记录会经历加锁再快速解锁的历程
序号 trx 1 trx 2 ① begin; ② begin; ③ select * from student where id <= 5 lock in share mode; ④ select * from student where id = 7 for update; 两个事务均可以正常执行,加锁示意如下
序号 trx 1 trx 2 ① begin; ② begin; ③ select * from student where id = 7 for update; ④ select * from student where id <= 5 lock in share mode; 此时会发现trx 1被阻塞等待,直至trx2事务提交或者回滚
select * from student where id >= 5;
依据聚簇索引根节点定位到符合查询条件的第一条记录(id = 5的记录),加上S记录锁;判断是否符合索引下推条件;引擎层判断是否符合范围查询边界;server层继续判断是否符合查询条件中非索引下推条件;根据记录单向链表依次往后找,重复上诉步骤,直至最后一条supermum记录,引擎层识别出为supermum记录,返回server层查询结束标记,且不会对supermum加锁。
select ... for update 加锁过程与上诉相似,唯一差别就是加X记录锁
- update
- 不涉及二级索引更新
加锁过程与select ... for update一致,只涉及聚簇索引加锁;
update student set country = '楚' where id <= 3;
我的理解,应该与上诉select * from student where id <= 5 lock in share mode;过程类似,逐步找到记录然后依次对聚簇索引加锁,对于边界值之后的一条记录“id=5”,也有个快速的加锁解锁过程, |序号|trx 1|trx 2| |---|---|---| |①|begin;| | |②| |begin;| |③|select * from student where id = 5 for update;| | |④| |update student set country = '楚' where id <= 3;| - 涉及二级索引更新
加锁过程与select ... for update相似,会先对聚簇索引加锁,然后再对更新列涉及的二级索引加锁。
update student set name = 'g关云长' where id <= 3;加锁示意图如下: - delete
与update涉及二级索引范围更新加过过程类似,先对聚簇索引加锁,再对关联的二级索引加锁;
|序号|trx 1|trx 2|
|---|---|---|
|①|begin;| |
|②| |begin;|
|③|select * from student where id = 5 for update;| |
|④| |delete from student where id <= 3;|
此时会发现④语句发生阻塞等待,
select * from information_schema.INNODB_LOCKS;
- 不涉及二级索引更新
加锁过程与select ... for update一致,只涉及聚簇索引加锁;
8.1.2、二级索引
8.1.2.1、 精确
- select ... lock in share mode/select ... for update
select * from student where name = 'g关羽';
- 从index_name定位到name=g关羽 记录加上S记录锁;
- 因为返回为*,不满足索引覆盖,需要走一次回表,对对应的聚簇索引同样加上S记录锁;
总结就是先对idx_name记录加锁在对关联聚簇索引加锁,索引示意图如下
select ... for update 加锁过程与上诉相似,唯一差别就是加X记录锁。
序号 trx 1 trx 2 ① begin; ② begin; ③ select name from student where name = 'g关羽' for update; ④ select * from student where id = 3 lock in share mode; ⑤ commit; ⑥ commit; 执行完第④步之后,会发现出现锁等待现象,
select * from information_schema.INNODB_LOCKS在同一个聚簇索引上分别加上了S,X锁;从而佐证二级索引加X记录,哪怕是符合索引覆盖,也是会对关联聚簇索引加锁。
- update 加锁过程与select ... for update精确查询类似,先对二级索引加锁,再对关联的聚簇索引加锁;如果更新字段涉及到其他二级索引,那么推测加锁顺序应该是 二级索引->聚簇索引->其他二级索引
- delete 加锁过程与update精确更新一致,先对二级索引加锁,再对聚簇索引加锁;涉及到其他二级索引,再对其他二级索引加锁。
8.1.2.2、范围
- select ... lock in share mode/select ... for update
select * from force index(idx_name) student where name <= 'c曹操';
- 从idx_name定位到第一条符合条件的记录(name = b白起),对该索引记录加S记录锁;
- 判断是否符合索引下推条件,此处符合索引下推条件;如果不满足范围边界条件,跳过到下一条,如果已经是查询的最后一条,返回给server层结束查询标识;()
- 因为返回的是*,所以需要执行一次回表,到对应的聚簇索引中拿到完整的记录,返回给server层,并且对聚簇索引中该记录加锁;
- server层拿到记录后,判断是否为结束查询标识,如果不是,再判断非索引下推条件的剩余查询条件是否满足,如果不满足释放掉聚簇索引上的锁;
- 依据idx_name单向链表,取下一条索引记录,依次执行2、3、4步骤;
上诉语句索引加锁示例图
select * from student force index(idx_name) where name >= 'g关羽';
与前面主键id>5过程类似,先在idx_name中定位到第一条记录,加S记录锁,然后再从聚簇索引中取相应记录,同样加S记录锁。直到走到idx_name中的supermum,识别出最后一条结束查询,且supermum上不会加锁。加锁示意图如下:select ... for update 加锁过程与上诉相似,唯一差别就是加X记录锁
- update 加锁过程与select ... for update范围查询类似,先对二级索引加锁,再对关联的聚簇索引加锁,如果更新字段涉及其他二级索引,再对其他二级索引加锁;
例如
update student set country = '汉' where name <= 'c曹操';select ... lock in share mode/for update;
序号 trx 1 trx 2 ① begin; ② begin; ③ update student set country = '汉' where name <= 'c曹操'; ④ select * from student where name = 'd邓艾' lock in share mode;
- delete 加锁过程与update范围更新一致,先对二级索引加锁,再对关联的聚簇索引加锁,如果涉及其他二级索引,再对其他二级索引加锁;
例如
delete from student where name <= 'c曹操';select ... lock in share mode/for update;
序号 trx 1 trx 2 ① begin; ② begin; ③ delete from student where name <= 'c曹操'; ④ select * from student where name = 'd邓艾' lock in share mode;
8.1.3、无索引
8.1.3.1、精确
- select ... lock in share mode/select ... for update
select * from student where country = '蜀' lock in share mode;
因为无索引,只能全表扫描,MySQL如下操作:
- 从聚簇索引取第一条记录,加S记录锁,返回给server层;
- server层判断是否符合查询条件,如果成立返回给客户端;如果不符合,释放锁;
- 从聚簇索引取下一条,重复上诉步骤;
- update 与select ... lock in share mode/for update, 加锁过程一致,依次遍历聚簇索引,加S记录锁,判断不符合则释放锁;如果更新列涉及到其他二级索引,再对关联二级索引加锁。
- delete 与上诉update加锁一致
8.1.3.2、 范围
- select ... lock in share mode/select ... for update 与无索引精确select一致
- update 与无索引精确update一致
- delete 与无索引精确delete一致
8.1.4、insert
8.1.4.1、主键insert
主键插入流程图
8.1.4.2、唯一索引insert
插入流程与主键相似, 差别在于出现重复记录,请求的是S临键锁
8.2、RR级别
8.2.1、主键
8.2.1.1、精确
- select ... lock in share mode/for update
select * from student where id = 3 lock in share mode;加锁示意图与RU/RC级别一致,都是在对应聚簇索引上加S记录锁;
select * from student where id = 2 lock in share mode;RR级别下因为要解决幻读的问题,所以需要对下一条记录上加GAP锁,加锁示意图如下而RC则不需要加任何锁;
- update
- 不涉及二级索引列更新 与select ... lock in share mode/for update相似,对于关联聚簇索引加X记录锁;如果不存在也会对下一条记录加GAP锁(superman好像加的是next-key锁)
- 涉及二级索引列更新 如果涉及到二级索引列更新,那么在聚簇索引加X记录锁,再对涉及得二级索引加X记录锁;
- delete 对聚簇索引加X记录锁,再对涉及得二级索引加记录锁;
8.2.1.2、范围
- select ... lock in share mode/for update
select * from student where id <= 3 lock in share mode,RR级别下与RC不同之处会对于符合记录加next-key锁,且边界记录的下一条记录也会加next-key锁(),加锁示意图如下select * from student where id >= 7 lock in share mode;与上一语句不同点在于边界值的处理,id=7加S记录锁,id=10,supermum加next-key锁,加锁示意图如下 - update
- 更新列不涉及二级索引 与select ... for update 加锁情况一致;
- 更新列涉及二级索引
聚簇索引的加锁情况与select ... for update一致,同时对关联的二级索引加X记录锁;例如:
update student set name = contact(name, '1') where id >= 7;加锁示意图如下:
- delete 与"更新列涉及二级索引"的update,加锁一致
8.2.2、唯一二级索引
将idx_name调整为唯一二级索引
8.2.2.1、精确
- select ... lock in share mode/for update
select * from student name = 'c曹操' lock in share mode;唯一索引,RR级别加锁与RC很相似,先对唯一索引加S记录锁,再对关联聚簇索引加S记录锁;加锁示意图select * from student where name = 'f法正' lock in share mode;对于不存在的记录,RR级别会在下一记录上加上Gap锁(这样可以避免幻读),因为不存在的记录,所以不会在聚簇索引加锁。加锁示意图如下: - update 与select ... for update,加锁类似;都是先对唯一索引加X记录锁,再对聚簇索引加X记录锁(如果再涉及其他二级索引,再对涉及的二级索引加X记录锁)
- delete 与update加锁类似
8.2.2.2、范围
- select ... lock in share mode/for update
select * from student where name <= 'c曹操' lock in share mode;唯一索引加锁过程与主键范围查询类似,然后再对对应聚簇索引加锁。加锁示意图如下select * from student where name >= 'g关羽' lock in share mode;唯一索引加锁过程与主键范围查询类似(此处很神奇),再对对应聚簇索引加锁。加锁示意图如下 - update
- 更新列不涉及其他二级索引 与select ... for update;加锁类似
- 更新列涉及其他二级索引 与select ... for update;加锁类似,再对其他二级索引加X记录锁
- delete 与“更新列涉及其他二级索引”加锁类似
8.2.3、普通二级索引
将idx_name调整为普通索引
8.2.3.1、精确
- select ... lock in share mode/for update
select * from student where name = 'c曹操' lock in share mode;此处与主键和唯一索引的加锁差异较大,name='c曹操' 加S next-key锁,同时下一条记录加gap锁;加锁示意图如下:
为啥在本记录加了记录锁之后,才需要对本记录加gap锁?我的理解还是为了解决幻读问题,因为普通二级索引允许重复值,且MySQL加锁加在索引记录上,只有把前后的间隙均锁住,才能避免幻读的发生;
select * from student where name = 'f法正' lock in share mode;
对下一条记录加gap锁,加锁示意图如下
- update
- 更新列不涉及其他二级索引 与select ... for update加锁一致
- 更新列涉及其他二级索引 与select ... for update加锁一致,在此基础上再对其他二级索引加X记录锁
- delete 与“更新列涉及其他二级索引”的update 加锁一致
8.2.3.2、范围
- select ... lock in share mode/for update
select * from student where name <= 'c曹操' lock in share mode;加锁示意图select * from student where name >= '关羽' lock in share mode;加锁示意图 - update
- 更新列不涉及其他二级索引 与select ... for update 加锁一致
- 更新列涉及其他二级索引 与select ... for update 加锁类似,先对索引加临键锁,再对聚簇索引加记录锁,再对涉及的其他二级索引加记录锁
- delete 与“更新列涉及其他二级索引”update 加锁一致
8.2.4、无索引
移除idx_name
8.2.4.1、精确
- select ... lock in share mode/for update
select * from student where name = 'c曹操' lock in share mode;,加锁示意图
对于无索引的查询,只能逐条遍历索引,因为RR级别需要解决幻读的问题,只能将所有间隙都锁住,避免在一个事务未结束时,其他事务新增记录,带来幻读问题 对于不存在的记录查询,加锁与上面一致,也是会锁住所有记录
,也会对所有聚簇索引加锁,但是如果不符合会立马释放掉,因为RC没有禁止幻读的诉求。
- update
- 更新列不涉及其他二级索引 与select ... for update加锁一致
- 更新列涉及其他二级索引 举措索引加锁与select ... for update一致,再对涉及二级索引加记录锁;
- delete 与“更新列涉及其他二级索引”的update加锁过程一致
8.2.4.2、范围
- select ... lock in share mode/for update
- update
- delete RR级别无索引条件下,与精确场景下加锁类似
8.2.5、insert
8.2.5.1、主键insert
主键插入流程图
对于主键插入,RC/RR差别不大
8.2.5.2、唯一索引insert
插入流程与主键相似, 差别在于出现重复记录,请求的是S临键锁
唯一索引的插入,RR与RC加锁也基本一致
9、死锁
MySQL死锁现象,比较好理解,两个事务相互等待各自持有的锁,就会导致死锁; MySQL解决死锁的两种方式:
- 锁超时机制:事务在等待锁时,超出一定时间内自动放弃锁等待;
- 外力干预解除死锁:MySQL通过wait-for graph主动检测死锁,强制将导致死锁的某个事务结束;
9.1、锁超时机制
MySQL中默认锁等待超时时间=50s
9.2、死锁检测机制
- wait-for graph 何时触发 mysql 在事务请求锁无法立即获取而需要进入等待时,会触发wait-for graph算法,来检测是否存在死锁;如果存在,那么会选择将其中的小事务进行回滚,来解决死锁问题;
- wait-for graph 怎么检测死锁
MySQL会分别记录事务所持有锁信息以及所需要等待的锁;
锁的信息链表:每个锁持有的事务是谁;
事务等待链表:每个阻塞等待事务等待的锁是谁;
以上图为例,会发现T1->T2->T3->T1,锁的等待会形成一个闭环,那么MySQL就判断发生了死锁,会选择其中的小事务进行回滚,解决死锁;
- 如何判定小事务? MySQL更改行数据会产生相应的undo-log,undo-log少的为小事务,如果undo-log量相同,那么选择造成死锁的事务;
9.3、常见的几种死锁情况
参见《Mysql常见死锁分析》