Mysql锁之——行锁

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: Mysql锁之——行锁

这篇文章我们来聊聊Mysql的行锁

InnoDB的行锁包括:记录锁、间隙锁(Gap Lock,解决幻读问题)和组合锁。

两阶段锁协议

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

行锁

顾名思义,就是给一行数据记录上锁。

间隙锁

注意:以下所有例子都是默认在RR隔离级别下:

// 创建表 t 
mysql> 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;
// 插入记录
mysql> INSERT INTO t values(0,0,0),(5,5,5),(10,10,10),(15,15,15),
    (20,20,20),(25,25,25);

假设,只给d=5这条记录上锁,执行以下语句:

mysql> select * from t where d=5 for update;

什么是幻读?如上图:

  • 事务A 执行select * from t where d=5 for update,查询d=5的所有记录
  • 事务B将id=0的d字段修改5,事务A查询到两条结果
  • 事务C插入一条记录,也满足事务A的查询条件,事务A查询到三条结果。

幻读

  • 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,“幻读”只有在当前读下才会出现;
  • 幻读专指新插入的行。

解决

幻读产生的原因是,行锁只能锁住行,而插入语句是要插入到现有数据记录的“间隙”。因此,为了解决幻读,InnoDB引入了间隙锁

间隙就是两个值之间的间隙,如开头创建的表t,插入了6条记录,则产生7个间隙,都是开区间,如下图:

分析:select * from t where d=5 for update;

实际上,因为d上没有索引,所以会走全表扫描,在一行一行的扫描过程中,不仅给每行加上锁,而且给两个值之间的间隙也加了间隙锁,这样就可以确保无法插入新的数据记录。

组合锁(Next-key lock)

就是间隙锁+行锁,前开后闭区间。如:(5,10], (10, 15],(15, 20],(20, 25]。

加锁规则

  1. 加锁的单位是next-key-lock;
  2. 只有访问到的对象才会加锁;
  3. 对于唯一索引的等值查询来说,next-key-lock会退化为行锁;
  4. 索引的等值查询来说,向右遍历时,右边界不满足等值条件时,next-key-lock会退化为间隙锁;
  5. 对于唯一索引的范围查询来说,会访问到第一个不满足条件的记录为止。

例一:主键索引等值锁

分析:

  • 根据规则1,Session A会在主键id上 加 (5,10] 的 next-key lock;
  • 因为Session A的next-key lock,Session B的插入会进入锁等待,直至超时;
  • 根据规则4,向右遍历的右边界10,不满足等值条件id=7,所以next-key lock退化为间隙锁(5,10),所以,Session C的更新可以成功。

例二:覆盖索引等值锁

分析:这里 session A 要给索引 c 上 c=5 的这一行加上读锁。

  • 根据规则1,会在索引c上加(0,5] 的 next-key lock;
  • 因为c是普通索引,到c=5这条记录不会停下来,会继续往后扫描,直至不满足等值条件,也就是 c=10 为止。根据规则2,还会在索引c上加(5,10]的next-key lock;
  • 根据规则4,继续向后遍历,右边界10不满足等值条件,该next-key lock退化为间隙锁(5,10);
  • 根据规则2,只有在访问到的对象才会加锁。注意此时的查询用到了覆盖索引,并不需要访问主键索引,所以主键索引上没有任何锁。所以Session B的update语句可以执行成功。但是Session C的插入语句会被 Session A 的间隙锁锁住。

注意,这个例子中是以 lock in share mode 方式加锁,这种加锁方式默认不会修改数据,只会锁覆盖索引。如果是以 for update 方式,系统会认为你接下来会修改数据,顺便就主键索引上满足条件的行加上行锁。

例三:主键索引范围锁

先来看看,以下两条语句有什么不同吗?

mysql> select * from t where id=10 for update;
mysql> select * from t where id>=10 and id<11 for update;

对于整型字段来说,上述两条语句在逻辑上执行结果是一致的,但是加锁的范围却不同。下面,我们来看看第二条语句的加锁效果:

分析:

  • 根据规则1,会在主键索引id上加(5,10] 的 next-key lock;
  • 根据规则3,唯一索引的next-key lock会退化为行锁,也就是只加了id=10的行锁;
  • 因为是范围查询,继续向后扫描,找到id=15停下来,根据规则2,会在id上加(10,15] 的next-key lock;

所以,Session A这时候的锁范围就在主键索引id=10的行锁,以及(10,15]的next-key lock;这样SessionB 和 C的执行结果你就理解了。

例四:非唯一索引范围锁

与例子三不同的是,用的是非唯一索引c。

select * from t where c>=10 and c<11 for update;

分析:

  • 根据规则1,会在索引c上加(5,10] 的next-key lock;
  • 因为c不是唯一索引,所以规则3不适用。
  • 因为范围查询,继续向后扫描,找到c=15停下来。根据规则2,会在c上加(10,15] 的next-key lock;

所以,Session A就加了(5,10] 和 (10,15] 的next-key lock; 那么此时主键索引上有那些锁呢?我们继续来验证:

可以看到,主键id=10的记录被加了锁。

例五:唯一索引范围锁Bug

mysql> select * from t where id >10 and id <=15 for update;

分析:

  • 根据规则1,会在id索引上加(10,15] 的 next-key lock;因为id是唯一键,所以到id=15这就结束了。但是,InnoDB 会继续往后扫描,直到第一个不满足条件的记录为止,也就是id=20。所以,根据规则2,就需要给id索引加上(15,20] 的next-key lock。

所以,Session B 对于id=20行记录的update会被阻塞,同样,Session C的插入语句也会被阻塞。

例六:非唯一索引上的“等值”的例子

先给表t插入一行记录

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

新插入的行数据c=10,此时表中就有两条c=10 的记录,分别是(10,10,10)和 (30,10,30)。但是,这两条记录的主键值不同,也就是说,这两个c=10的记录中间也有间隙。

接下来我们来看例六:

分析:

  • 根据规则1,会在索引c上加(5,10] 的 next-key lock;
  • 因为是非唯一索引,会继续往后扫描,找到下一个c=10&&id=30的记录,继续往后扫描,直到不满足等值条件id=15时停止,根据规则2,加上(10,15] 的 next-key lock;
  • 因为是等值查询,根据规则4,向右扫描,右边界15不满足等值c=10条件,next-key lock退化为间隙锁(10,15);

所以,Session B被阻塞,Session C执行成功。加锁范围如下:

例七:limit语句

分析:

虽然,你知道只有两条满足条件的记录,两个语句的执行逻辑是一致的,但是与例六不同的是,加了limit 2限定,在找到两条c=10的记录后,就不再往后扫描了,所以加锁范围也有了不同,如下:

例八:死锁的例子

分析:

  • Session A给索引c,加了(5,10] 的next-key lock,和 (10,15)的间隙锁;
  • Session B也要加(5,10] 的 next-key lock,阻塞等待;
  • Session A的插入也被阻塞;

很好的说明了,next-key lock等价于 “行锁 + 间隙锁”。实际上,Session B的加锁是分为两步的,先加(5,10)的间隙锁,间隙锁和间隙锁不冲突,添加间隙锁成功,再去申请行锁的时候,阻塞了。从而导致了Session A 往这个间隙的插入操作阻塞了,形成死锁,InnoDB 让事务B回滚。

补充知识:

可重复读隔离级别下,普通的查询(select … from t where …;)是快照读,不会加锁,也就是利用MVCC读取数据。

当前读:select…for update 是当前读,当前读就是能读到所有已提交的记录的最新值。

加锁介绍

select … where…lock in share mode,这种方式会加S共享锁

select…where…for update,这种方式会加X排它锁,

如果是update、delete都会加X排他锁,具体这个锁是加在聚簇索引还是二级索引,以及对应索引的哪些记录上,由过滤条件:1.是哪种索引(主键索引、唯一索引、非唯一索引);2.事务的隔离级别来决定。

X锁 加锁过程

  1. 主键索引:在符合条件的记录上加X锁
  2. 非主键唯一索引:在符合条件的唯一辅助索引的索引记录上加X锁,并在对应的主键索引的记录上加X锁,若是查询用到了覆盖索引,并且使用的lock in share mode则不会给主键索引对应的记录加锁。
  3. 非唯一索引:
    (RC)符合条件的索引记录上都加X锁,对应的主键索引上也加X锁
    (RR)符合条件的索引记录上都加X锁,对应的聚簇索引上也加X锁,此外,二级索引符合条件的前后的间隙都会加上间隙锁,防止幻读。
  4. 无索引:
    (RC)主键索引的所有记录都会加X锁
    (RR)主键索引的所有记录都会加X锁,而且所有的间隙都会加间隙锁。

笔记参考于极客时间《MySQL实战45讲》


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
20天前
|
关系型数据库 MySQL
【MySQL实战笔记】07 | 行锁功过:怎么减少行锁对性能的影响?-01
【4月更文挑战第18天】MySQL的InnoDB引擎支持行锁,而MyISAM只支持表锁。行锁在事务开始时添加,事务结束时释放,遵循两阶段锁协议。为减少锁冲突影响并发,应将可能导致最大冲突的锁操作放在事务最后。例如,在电影票交易中,应将更新影院账户余额的操作安排在事务末尾,以缩短锁住关键行的时间,提高系统并发性能。
15 4
|
2天前
|
存储 算法 关系型数据库
MySQL事务与锁,看这一篇就够了!
MySQL事务与锁,看这一篇就够了!
|
7天前
|
存储 关系型数据库 MySQL
MySQL的锁机制
MySQL的锁机制主要用于管理并发事务对数据的一致性和完整性的访问控制
23 4
|
12天前
|
存储 SQL 关系型数据库
|
16天前
|
关系型数据库 MySQL 数据库
MySQL锁解密:读锁与写锁
【4月更文挑战第20天】
24 1
|
16天前
|
关系型数据库 MySQL 数据库
|
16天前
|
算法 关系型数据库 MySQL
|
16天前
|
SQL 关系型数据库 MySQL
MySQL锁:解析隐式锁与显式锁
【4月更文挑战第20天】
38 0
|
16天前
|
关系型数据库 MySQL 数据库
|
16天前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL 锁
18 1