MYSQL的加锁规则

丁奇老师总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。

  • 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
  • 原则 2:查找过程中访问到的对象才会加锁。
  • 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

下面示例的建表语句

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

等值查询不存在的行, 间隙锁

mysql如何加锁 mysql加锁规则_加锁

  • 由于等值查询id=7的记录不存在, 此时会把包含id=7的这个间隙加上Next-key Lock(范围锁) , 就是(5,10]。
  • 由于id=10这个记录明显不符合id=7的条件, 所以范围锁退化成间隙锁, 只锁住(5,10)

非唯一索引等值锁

mysql如何加锁 mysql加锁规则_mysql如何加锁_02


这个例子中, 先加上间隙锁(0,5] , 由于非唯一索引, 查到5之后还会继续往后查找,所以(5,10]之间不会加锁, 又由于c=10明显不符合。 所以范围锁退化成(0,9)了。

由于范围锁,间隙锁,行锁等都是加载索引上的,刚好session A的所有操作只涉及到覆盖索引,所以session A的中的锁都是加载覆盖索引上, 这就是为何B事务可以成功的原因,因为B操作没有涉及到A的覆盖索引。

这个例子中有点难以理解, 按照丁奇老师的课程所说, 由于使用了覆盖索引, 所有的数据都不用回表操作, 所以只要锁住覆盖索引就可以了,主键索引不用管。

所以这个例子中session B的操作并没有改动覆盖索引, 所以执行session B操作可以成功。

session C的操作就会被阻塞住,因为C中的操作会涉及到修改覆盖索引, 被session A锁住了。

主键索引范围锁

mysql如何加锁 mysql加锁规则_主键_03

现在我们就用前面提到的加锁规则,来分析一下 session A 会加什么锁呢?

  1. 开始执行的时候,要找到第一个 id=10 的行,因此本该是 next-key lock(5,10]。 根据优化 >1, 主键 id 上的等值条件,退化成行锁,只加了 id=10 这一行的行锁。
  2. 范围查找就往后继续找,找到 id=15 这一行停下来,因此需要加 next-key lock(10,15]。所以,session A 这时候锁的范围就是主键索引上,行锁 id=10 和 next-key lock(10,15]。这样,session B 和 session C 的结果你就能理解了。这里你需要注意一点,首次 session A 定位查找 id=10 的行的时候,是当做等值查询来判断的,而向右扫描到 id=15 的时候,用的是范围查询判断。

上面是丁奇老师的课程中说的内容。 我就不做自己的阐述了。

非唯一索引范围锁

mysql如何加锁 mysql加锁规则_主键_04

  1. A事务会根据c=10 加上(5,10]的范围锁,
  2. 由于需要查小于11的记录,11又是不存在的,所以会锁住(10,15]之间的范围锁
  3. 由于c 索引不是唯一索引, 所以上面两个范围锁都不会退化成行锁, 所以综合加锁范围是(5,15]

唯一索引范围锁 bug

mysql如何加锁 mysql加锁规则_加锁_05

非唯一索引上存在"等值"的例子

insert into t values(30,10,30);

进行插入操作,后面的所有图片的示例都是加上了这个值后的操作

mysql如何加锁 mysql加锁规则_mysql如何加锁_06

mysql如何加锁 mysql加锁规则_mysql如何加锁_07

limit 语句加锁

mysql如何加锁 mysql加锁规则_主键_08

mysql如何加锁 mysql加锁规则_死锁_09

一个死锁的例子

mysql如何加锁 mysql加锁规则_加锁_10

间隙锁之间是不互斥的,同一个间隙可以被多个事务进行加锁操作。

  • 其实(5,10]范围锁, 是先加间隙锁(5,10) 在加上10这条记录上的行锁的。
  • 所以B事务上整个语句虽然会被阻塞, 但是B事务中的(5,10)之间的间隙锁是添加成功了, 阻塞的只是10这个记录上的行锁。
  • 因此A事务在此进行插入操作的时候会进入死锁,这个时候MYSQL就会让阻塞主的事务B回滚了

注意: 间隙锁之间虽然不会互斥, 但是间隙锁和写锁之间会互斥的,这样就会很容易导致死锁问题