聊聊MySQL的加锁规则《死磕MySQL系列 十五》

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 聊聊MySQL的加锁规则《死磕MySQL系列 十五》

大家好,我是咔咔 不期速成,日拱一卒


本期来聊聊MySQL的加锁规则,知道这些规则后可以判断SQL语句的加锁范围,同时也可以写出更好的SQL语句,防止幻读问题的产生,在能力范围内最大程度的提升MySQL并发处理事务能力。


现在你应该知道了MVCC解决了快照读下的幻读问题,但当前读的幻读问题还是基于锁解决的,也就是next-key lock。






一、了解next-key lock

在文章幻读:听说有人认为我是被MVCC干掉的这期文章中,详细说明了幻读在当前读、快照读下的解决方式。


快照读简单来说就是简单的select操作,没有加任何锁,在Innodb存储引擎下执行简单的select操作时,会记录下当前的快照读数据,之后的select会沿用第一次快照读的数据,即使有其它事务提交也不会影响当前的select结果,因此通过快照读查询的数据虽然是一致的,但有可能不是最新的数据,而是历史数据。


这个是从官方文档中获取的资料,解释在当前读下Innodb使用next-key lock锁来解决幻读问题。


To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking. InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. In addition, a next-key lock on an index record also affects the “gap” before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.


大致意思,为了防止幻读,Innodb使用next-key lock算法,将行锁(record lock)和间隙锁(gap lock)结合在一起。Innodb行锁在搜索或者扫描表索引时,会在遇到的索引记录上设置共享锁或者排它锁,因此行锁实际是索引记录锁。另外, 在索引记录上设置的锁同样会影响索引记录之前的“间隙(gap)”。即next-key lock是索引记录行加上索引记录之前的“gap”上的间隙锁定。


二、next-key lock 加锁规则

加锁规则总结为以下几点,不同MySQL版本会有微小的差异


查询过程中只要访问的数据都会加锁,加锁的基本单位是next-key lock,左开右闭

唯一索引等值查询,next-key lock退化为行锁

索引等值查询,需要访问到第一个不满足条件的值,此时的next-key lock会退化为间隙锁

索引范围查询需要访问到不满足条件的第一个值为止

之前看过丁老师的文章说是在唯一索引下,范围查询会访问到不满足条件的第一个值为止,这个问题在MySQL8.0.18已经修复了


目前咔咔使用的MySQL版本是 8.0.26 ,接下来根据这几条规则设计几条SQL,一起来看看都锁了那些数据。


创建next_key_lock表,建表的初始化语句如下。


CREATE TABLE `next_key_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `class` tinyint(4) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_class` (`class`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO next_key_lock (`class`,`name`) VALUES (1,'咔咔'),(3,'小刘'),(8,'小张'),
(15,'小李'),(20,'张但'),(25,'王五'),(25,'李四');



三、唯一索引等值查询

下图是SQL的执行流程,分为了三个终端,按照终端顺序来执行SQL


image.png


分析这条SQL满足那些规则


规则一:查询过程中只要访问到的数据都会加锁,加锁的基本单位是next-key lock,左开右闭状态。


规则二:唯一索引等值查询,next-key lock退化为行锁。


规则三:索引等值查询,需要访问到第一个不满足条件的值,此时的next-key lock会退化为间隙锁


根据规则一,加锁范围为(7,∞]


根据规则二,退化为行锁,但明显此条SQL不满足条件,因为表里边就不存在id=9的这条记录,所以此条规则不生效


根据规则三,next-key lock退化为间隙锁,加锁范围为(7,∞)


结论


得知唯一索引等值查询时,行数据存在的时候是行锁,行数据不存在,那就是间隙锁。


因此终端2的语句会一直处于等待状态,直到终端1执行完成。


四、普通索引等值查询


image.png

分析这条SQL满足那些规则


规则一:查询过程中只要访问到的数据都会加锁,加锁的基本单位是next-key lock,左开右闭状态。


规则二:索引等值查询,需要访问到第一个不满足条件的值,此时的next-key lock会退化为间隙锁


根据规则一,加锁范围是(3,8]


根据规则二,需要访问到第一个不满足的值,加锁范围(8,15],又因为会退化为间隙锁,加锁范围变为(8,15)


结论


三条SQL执行后,你看到的现象是MySQL2执行成功,MySQL3SQL等待


MySQL3要加入的值是9,在锁范围内所以需要等MySQL1提交事务后才可执行成功。


为什么MySQL2为什么会执行成功


总结的加锁规则中,查询过程中访问到的数据都会加锁,但MySQL2使用的覆盖索引,所以并不需要回表查询主键索引,所以主键索引上是没有加任何锁的。


你要理解这块就需要知道主键索引、普通索引的索引结构,在B+tree中主键索引叶子节点存储的是整行数据,而普通索引叶子节点存储的是主键的值。


扩展


现在你知道了在这个例子中,lock in share mode值锁覆盖索引,但是如果是for update就会给主键索引上满足条件的行加上行锁。所以你也知道了使用了覆盖索引是避免不了数据被更新的,若想实现数据避免更新就需要绕过覆盖索引的优化。


现在你应该知道使用for update会给主键索引加锁,如果查询条件为普通索引但值是存在多个相同数据的,此时的加锁就会根据主键索引加锁。


五、主键索引范围锁


image.png


从上图得知MySQL2和MySQL3都处于等待MySQL1中


分析这条SQL满足那些规则


规则一:访问到的数据都会加锁


规则二:唯一索引等值查询,next_key_lock退化为行锁


规则三:索引范围查询需要访问到不满足条件的第一个值为止


根据规则一,加锁范围(7,8]


根据规则二,退化为行锁,加锁范围只是id=8这一行(后边解释)


根据规则三,范围查询就往后继续找,加锁范围(8,∞]


结论


此条SQL加锁范围,行锁id=8,next_key lock(8,∞]


问题:为什么从next-key lock退化为行锁


首先你需要明白所谓的等值判断和范围判断,指的是这一行数据被查询选中的时候走的判断条件是通过 a=b 还是 a>b或a<b 来确定的,直白点就是这行数据是通过等值来的还是范围查询来的。


从SQL返回结果可得知数据是根据id=8来的,因此next-key lock会退化为行锁。


六、普通索引范围锁

执行SQL为


select * from next_key_lock where class >= 8 and class<10 for update;

可以看到这个SQL跟第五案例的MySQL1的唯一区别是普通索引没有退化行锁的规则。


分析这条SQL满足那些规则


规则一:索引等值查询需要访问到第一个不满足的值,next_key lock 退化为间隙锁


规则二:索引范围查询需要访问到不满足条件的第一个值为止


根据规则一,加锁范围(7,8]


根据规则二,加锁范围(8,15]


结论


加锁范围为(7,8]、(8,15]


问题:为什么没有退化为间隙锁


仔细看规则,索引等值查询需要访问到不满足的值才会退化为间隙锁,此时是可以访问到8这个数据的,因此不会退化为间隙锁。


七、普通索引倒叙范围锁

在以上的所有案例中都是默认正序规则,接下来看下倒叙时的加锁规则是怎么样的


执行SQL为


select * from next_key_lock where class >= 15 and class<=20  order by desc lock in share mode;


由于SQL加上了order by ,因此第一个要定位class索引最右边的值,也就是class=20,因为class是普通索引等值查询,因此会加上next-key lock 左开右闭(15,20],普通索引等值查询会访问到不满足条件的值为止,所以还会继续扫描,直到遇到25,又会加上一个next-key lock (20,25],又因为25不满足查询条件,因此会退化为间隙锁(20,25)


还有一个条件是class >= 15,向左扫描到class = 8才会停下来知道了是小于15了,加锁单位是next-key loc ,左开右闭范围是(3,8]


又因为查询是*,绕过了覆盖索引,需要回表查询,因此给主键ID也会加锁,加锁为id=4,id=5两个行锁。


结论


因此这条SQL加锁范围在索引class是(3,25),主键索引上id=4,5两个行锁。


八、总结

本期文章带大家了解next_key lock的加锁范围,并且给大家总结了四条加锁规则,经过五个实战案例给再给大家说几个注意点。


唯一索引等值查询时next-key lock退化为行锁,这里指查询到数据,若没有查到数据则依然是间隙锁


普通索引等值查询next-key lock退化为间隙锁


最后一点当SQL加上排序时加锁规则会有一定的变化,在后期文章中咔咔也会不断的提供很多案例供大家查看。


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
59 3
|
3月前
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
160 1
|
3月前
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
306 2
|
3月前
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
101 1
|
4月前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
364 5
|
3月前
|
存储 关系型数据库 MySQL
MySQL锁,锁的到底是什么?
【10月更文挑战第16天】MySQL 锁锁定的是与数据和资源相关的对象,其目的是为了保证数据的一致性、避免冲突,并在并发环境下合理协调事务或操作的执行。理解锁的对象和意义对于优化数据库性能、处理并发问题至关重要。
105 0
|
3月前
|
关系型数据库 MySQL 数据库
mysql锁详解
通过理解并合理运用MySQL中的锁机制,开发者可以有效管理数据库并发访问,平衡性能与数据一致性需求。更多关于MySQL锁的深入探讨和最佳实践,请参考专业的数据库管理资源[[深入MySQL锁机制详解
64 0
|
4月前
|
关系型数据库 MySQL 数据库
Mysql的锁
本文介绍了MySQL中表级锁和行级锁的区别,其中MyISAM仅支持表级锁,而InnoDB支持表级锁和行级锁,默认为行级锁。表级锁锁定整个表,实现简单,资源消耗少,但并发度低;行级锁仅锁定相关记录,减少冲突,提高并发度,但加锁开销大。此外,还介绍了共享锁和排他锁的概念及意向锁的作用。
|
4月前
|
存储 SQL 关系型数据库
MySQL 的锁机制,那么多的锁,该怎么区分?
MySQL 的锁机制,那么多的锁,该怎么区分?
52 0
|
5月前
|
监控 关系型数据库 MySQL
在Linux中,mysql的innodb如何定位锁问题?
在Linux中,mysql的innodb如何定位锁问题?