一、死锁模拟复现

1、当前自己电脑的mysql版本8.0.22

MySQL缓存死锁 mysql 死锁原因_数据

2、数据库的隔离级别--可重复读(默认隔离级别)

MySQL缓存死锁 mysql 死锁原因_MySQL缓存死锁_02

3、自动提交关闭

MySQL缓存死锁 mysql 死锁原因_MySQL缓存死锁_03

4、表结构,age为非唯一索引,对下面整个案例非常重要

MySQL缓存死锁 mysql 死锁原因_数据_04

MySQL缓存死锁 mysql 死锁原因_MySQL缓存死锁_05

5、

MySQL缓存死锁 mysql 死锁原因_记录锁_06

1、事务A执行更新操作,更新成功

2、事务B执行更新操作,更新成功

3、事务A执行插入操作,陷入阻塞

4、事务B执行插入操作,插入成功,同时事务A的插入由阻塞变为死锁error,事务A的插入操作变成报错

最终结果如下:

MySQL缓存死锁 mysql 死锁原因_数据_07

我们发现事务一被回滚,事务二执行成功

那既然是死锁,为什么回滚事务A,而不是事务B,是随机的还是有机制在里面?

我们可以理解死锁是数据库对事务的保护机制,一旦发生死锁,MySQL会选择相对小的事务(undo较少的)进行回滚

分析死锁日志

执行命令 :show engine innodb status

MySQL缓存死锁 mysql 死锁原因_死锁_08

MySQL缓存死锁 mysql 死锁原因_数据库_09

二、介绍mysql中的锁

1、mysql中的锁

MySQL缓存死锁 mysql 死锁原因_数据库_10

记录锁

记录锁其实很好理解,对表中的记录加锁,叫做记录锁,简称行锁

例如:

select * from user where id = 1 for update;

UPDATE SET age = 50 WHERE id = 1;

需要注意的是:

  • id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁(有关临键锁下面会讲)。
  • 同时查询语句必须为精准匹配(=),不能为 >、<、like等,否则也会退化成临键锁

记录锁是锁住记录,锁住索引记录,而不是真正的数据记录. 如果要锁的列没有索引,进行全表记录加锁

间隙锁(Gap Locks)

幻读例子

MySQL缓存死锁 mysql 死锁原因_记录锁_11

间隙锁 是 Innodb 在 RR(可重复读) 隔离级别 下为了解决幻读问题时引入的锁机制。间隙锁是innodb中行锁的一种

请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据

存在以下间隙

(-∞, 1] (1, 4] (4, 7] (7, +supernum]

MySQL缓存死锁 mysql 死锁原因_数据库_12

临键锁(Next-Key Locks)

Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。也可以理解为一种特殊的间隙锁。通过临建锁可以解决幻读的问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。

需要强调的一点是,InnoDB 中行级锁是基于索引实现的


锁总结:

1、当使用唯一索引来等值查询的语句时, 如果这行数据存在,不产生间隙锁,而是记录锁

2、当使用唯一索引来等值查询的语句时, 如果这行数据不存在,会产生间隙锁

3、当使用唯一索引来范围查询的语句时,对于满足查询条件但不存在的数据产生间隙(gap)锁,如果查询存在的记录就会产生记录锁,加在一起就是临键锁(next-key)锁

4、当使用普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;

5、在没有索引上不管是锁住单条,还是多条记录,都会产生表锁

三、死锁的产生的原因

数据库中有两条数据 id主键,age是非唯一主键


补充:

通过事务A和事务B的update语句,我们可以发现其实它们都持有间隙锁(10,20)的这段范围,说明间隙锁范围是可以相互兼容的,意思就是只要你的10不在我(10,+∞)的间隙锁范围内,那就可以产生部分重合的间隙锁,也就是这里的(10,20)

四、实际开发中如何尽量避免死锁发生

1、不同的应用访问同一组表时,应尽量约定以相同的顺序访问各表。对一个表而言,应尽量以固定的顺序存取表中的行

2、在主键等值更新的时候,尽量先查询数据库中是否有没有满足的条件,如果没有就不用更新,存在才更新

3、尽量使用主键更新数据,因为主键是唯一索引,在等值查询能查看到数据的情况下,只会产生记录锁,不会产生间隙锁,这样产生的死锁概率就减少了,如果是范围查询一样产生间隙锁

4、避免长事务,小事务发生锁的冲突的概率较小

5、在允许幻读和不可重复度的情况下,尽量使用RC的隔离级别,避免gap lock造成的死锁,因为产生死锁经常都跟间隙锁有关,间隙锁的存在本身也是在RR隔离级别来解决幻读的一种措施

MySQL缓存死锁 mysql 死锁原因_MySQL缓存死锁_13