MySQL加锁过程分析

目录

  • 1 MySQL加锁规则
  • 2 案例分析
    • 2.1 数据准备
    • 2.2 案例1
    • 2.3 案例2
  • 3 总结如何分析加锁

最近在通过极客时间的专栏,MySQL45讲复习mysql,看完了锁的部分,结合原来看到《Mysql是怎样运行的书》,做了一些小总结

1 MySQL加锁规则

这里我们提到的加锁规则都是在隔离级别为可重复读的情况,具体规则如下:

  • 我们加锁的最基本的单位都是next-key lock,是对一个左开右闭的区间进行加锁

    这里注意next-key lock可以认为是一个复合锁,是record lock 和gap lock结合起来的锁;前者就是我们所说的记录锁,是加载在我们的行记录上面的,后者就是我们常说的间隙锁,是一个开区间(next-key lock是以最右边的记录为边界,所以这里也能够说明为啥锁住的是一个左开右闭的区间了)

  • 只有扫描到的记录才会加锁

    无论什么时候,都要明确一点,我们的锁是加在索引(这里也能看到反应了在innodb中索引即数据,数据即索引的特点)上的,我们在查记录的时候都是查询优化器选择好索引后在对应的索引上进行查询,这个时候我们在查询过程中扫描到的记录才可能会加锁

  • 对于唯一索引,在做等值查询的时候,next-key lock会退化成record-lock

    这也很好理解,唯一索引等值查询查询到的值必然只有一个,所以mysql在此处做了优化

  • 对于普通索引的等值查询,在扫描的第一个不符合条件的记录时,next-key lock退化成gap lock

    我们在扫描的时候根据索引的特点,扫描到不符合条件的记录时就会结束,但是为了防止幻读的发生,我们在扫描到的第一条不符合条件的记录后面会加上一个gap lock

    这里注意下什么是等值查询=和>=,<=的边界我们都认为是等值查询

  • 对于唯一索引,在进行范围查询时,会和普通索引一样扫描到第一个不符合条件的记录并加上gap lock

    这一条被mysql45讲的作者称为一个Bug,确实这点不符合上面四点的原则,当做成特殊情况记录下来即可

  • 对于DESC即从后往前扫描的语句,我们会在开始扫描时第一个不符合条件的记录上加上gap lock

    这一点也比较好理解,就是为了防止幻读

什么样的语句会加锁

  • 这里注意一下,除了普通的select语句其他的语句都会加锁,对于普通的select语句,我们有MVCC中的readview来帮助我们避免脏读,不可重复读现和幻读现象

2 案例分析

2.1 数据准备

数据直接用的mysql45讲中的例子,挑了一些自己觉得比较有意思的案例捋了一下自己的思路。

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);

2.2 案例1

session A session B session C
begin;
select id from t where c = 5 lock in share mode
update t set d=d+1 where id=5 (ok)
insert into t values(7, 7, 7) (block)

具体分析过程如下:

对于session A:

  • 首先确定这条语句会在索引c上进行查询
  • 首先查到c=5的记录在这条记录上加上next-key lock (0, 5]
  • 这里查到c=5的记录后我们要查的是id,所以这时会使用覆盖索引,所以此时mysql并不会回到聚簇索引上进行查询,即聚簇索引上的记录不会被加锁, 这就是为什么session b的更新语句可以成功
  • 由于c是普通索引,所以还可能存在c=5的记录就继续扫描,直到扫描的第一条不为5的记录位置,这里我们扫描到c=10的记录结束,在c=10的记录上加上next-key lock,(5, 10],由于做的是等值查询,所以该锁会退化成一个gap lock即(5, 10)
  • 最终锁的范围是索引c上:(0, 10) gap lock(所以session C会阻塞),c=5的记录上有record lock

注意点

这里我们用到了覆盖索引,要避免这样使得聚簇索引上不会被加锁,我们可以加上for update写锁,或者改写语句,在查询语句中加入索引c中没有的字段,这样就不能用覆盖索引了。

2.3 案例2

session A session B
begin
select * from t where c >= 15 and c <= 20 order by c desc lock in share mode
insert into t values(6, 6, 6) (block)

session B会阻塞,接下来我们来看为什么

session A的加锁过程如下

  • 选择查询索引 —— c
  • 这条语句是从右往左扫描的,所以开始扫描的第一条记录也就是c=25这条记录会加上gap lock (20, 25)
  • 然后扫描到第一条符合条件的记录c=20,加上锁(15, 20],同时会去聚簇索引上给我们的c=20的记录加上record lock
  • 然后扫描到第二条符合条件的记录c=15,加上锁(10, 25],同时回去聚簇索引上给我们的c=15的记录加上record lock
  • 然后再往前扫描,这是就扫描到的第一个不符合条件的记录c=10,给该记录加上锁(5, 10]

所以这里我们的sessionB会被block

3 总结如何分析加锁

这里总结以下自己看书后的心得,不一定对

加锁的分析一般有如下步骤

  • 弄清除这条语句会作用到哪个索引上,会不会进行回表操作,也就是会不会用到聚簇索引
  • 弄清除这条语句的扫描区间
  • 分析好扫描区间后,我们就可以按照上述的一些规则根据扫描的顺序,一条记录一条记录逐一分析

你可能感兴趣的:(mysql,后端)