前文提到,对于 InnoDB 来说,随时都可以加锁(关于加锁的 SQL 语句这里就不说了,忘记的小伙伴可以翻一下上篇文章),但是并非随时都可以解锁。具体来说,InnoDB 采用的是两阶段锁定协议(two-phase locking protocol):即在事务执行过程中,随时都可以执行加锁操作,但是只有在事务执行 COMMIT 或者 ROLLBACK 的时候才会释放锁,并且所有的锁是在同一时刻被释放。

并且,行级锁只在存储引擎层实现,而对于 InnoDB 存储引擎来说,行级锁又分三种,或者说有三种行级锁算法:

  • Record Lock:记录锁
  • Gap Lock:间隙锁
  • Next-Key Lock:临键锁

下面,我们来详细解释下这三种行锁算法。

Record Lock 记录锁

顾名思义,记录锁就是为某行记录加锁,事实上,它封锁的是该行的索引记录。如果表在建立的时候没有设置任何一个索引,那么这时 InnoDB 存储引擎会使用 “隐式的主键” 来进行锁定。

所谓隐式的主键就是指:如果在建表的时候没有指定主键,InnoDB 存储引擎会将第一列非空的列作为主键;如果没有的话会自动生成一列为 6 字节的主键。

那么,既然 Record Lock 是基于索引的,那如果我们的 SQL 语句中的条件导致索引失效(比如使用 or) 或者说条件根本就不涉及索引或者主键,行级锁就将退化为表锁。

Record Lock 示例

先来举个对索引字段进行查询的例子,有数据库如下,id 是主键索引:

CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

初始数据是这样的:

新建两个事务,先执行事务 T1 的前两行,也就是不要执行 commit

由于没有执行 commit,所以这个时候事务 T1 没有释放锁,并且锁住了 id = 1 的记录行,此时再来执行事务 2 申请 id = 2 的记录行:

可以看见,由于锁住的是不同的记录行,所以两个记录锁并没有相互排斥,来看一下现在表中的数据,由于事务 1 还没有 commit,所以应该是只有 id = 2 的 username 被修改了:

nice,果然。再执行下事务 1 的 commit,id = 1 的 username 也就被修改过来啦。

行锁退化为表锁示例

再来看下没有使用索引的例子:

同样的,新建两个事务,先执行事务 T1 的前两行,也就是不要执行 commit。我们试图使用 select ... for update 给 username = "user_three" 的记录行加上记录锁,但是由于 username 并非主键也并非索引,所以实际上这里事务 T1 锁住的是整张表:

由于没有执行 commit,所以这个时候事务 T1 没有释放锁,并且锁住了整张表。此时再来执行事务 2 试图申请 id = 5 的记录锁,你会发现事务 T2 会卡住,最后超时关闭事务:

两条不同记录拥有相同的索引,会发生锁冲突吗?

这个问题的答案应该很简单吧,上面我们强调过,行锁锁住的是索引,而不是一条记录(只不过我们平常这么说锁住了哪条记录,比较好理解罢了)。所以如果两个事务分别操作的两条不同记录拥有相同的索引,某个事务会因为行锁被另一个事务占用而发生等待

Gap Lock 间隙锁

这里我先简单提一嘴,下文会详细解释:不同于 Record Lock 是基于唯一索引的,Gap Lock 和 Next-Key Lock 都是基于非唯一索引的。

并且,不同于 Record Lock 锁定的是某一个索引记录,Gap Lock 和 Next-Key Lock 锁定的都是一段范围内的索引记录:

select * from test where id between 1 and 10 for update;

对于上述 SQL 语句,所有在(1,10)区间内(左开右开)的记录行都会被 Gap Lock 锁住,所有 id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条被操作的索引记录并不会被锁住

注意!这里指的是锁住所有的(1,10)区间内的 id,也就是说即使某个 id 目前并不在我们的表中比如 id = 6 ,如果你想插入一条 id = 6 的新纪录,那对不起,不行。

Next-Key Lock 临键锁

Next-Key Lock 是结合了 Gap Lock 和 Record Lock 的一种锁定算法,其主要目的是为了解决幻读问题

例如一个索引有 10,11,13 和 20 这四个值,分别对这个 4 个索引进行加锁操作,那么这四个操作分别对应的 Next-Key Lock 锁住的区间是:

  • (-∞, 10]
  • (10, 11]
  • (11, 13]
  • (13, 20]
  • (20, +∞]

细心的同学应该已经注意到了,和 Gap Lock 的不同之处就在于,Next-Key Lock 锁定的区间是左开右闭的,也就是说它是包含当前被操作的索引记录的。

在 InnoDB 默认的隔离级别 REPEATABLE-READ 下,行锁默认使用的算法就是 Next-Key Lock。但是,如果操作的索引是唯一索引或主键,InnoDB 会对 Next-Key Lock 进行优化,将其降级为 Record Lock,即仅锁住索引本身,而不是范围。

由于主键也是一种唯一索引,所以我们可以这么说:Record Lock 是基于唯一索引的,而 Next-Key Lock 是基于非唯一索引的

需要注意的,当操作的索引为非唯一索引时,InnoDB 会先用 Record Lock 锁住对应的唯一索引,再用 Next-Key Lock 和 Gap Lock 对这个非唯一索引进行处理,而不仅仅是锁住这个非唯一索引。具体地我们举个例子来看下。

Next-Key Lock 示例

假设我们为上面 test 表中新增一个字段,并设置为非唯一索引:

CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`class` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `index_class` (`class`) USING BTREE COMMENT '非唯一索引'
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

插入一些数据:

开启一个事务 1 执行如下的操作语句:

select * from test where class = 3 for update;

在这种情况下,InnoDB 事实上会加上三种行锁(select * ... from update 加的是行级写锁即 X 锁):

1)给主键索引 id = 105 加上 Record Lock

2)对于非唯一索引 class = 3,其加上的是 Next-Key Lock,锁定的范围是 (1,3]

3)另外,特别需要注意的是,InnoDB 存储引擎还会对非唯一索引 class 的下一个键值加上 Gap Lock(表中 class = 3 的下个键值是 6),所以还有一个 class 索引范围为 (3,6) 的间隙锁

总结下 2)和 3),对于这条 SQL 语句,InnoDB 存储引擎锁定地 class 索引范围是 (1, 6)

下面我们用实践来验证理论,再开启一个事务 2,执行下述的语句:

不出所料,由于在事务 1 中执行的 SQL 语句已经对主键索引中列 a=105 的记录加上了 X 锁,所以此处再去获取 这个记录的 X 锁会被阻塞住。

再用一个事务来执行下述 SQL 语句:

主键插入 104 没有任何问题,但是插入的 class 索引值 2 在被锁定的范围 (1,6) 中,因此执行同样会被阻塞住。

经过上面的分析,大家一定能够知道下面的 SQL 语句是可以正常执行的:

Attention

需要注意的是,Next-Key Lock 降级为 Record Lock 仅存在于操作所有的唯一索引列的情况。若唯一索引由多个列组成,而操作的仅是多个唯一索引列中的其中一个,那么 InnoDB 存储引擎依然使用 Next-Key Lock 进行锁定

关注公众号 | 飞天小牛肉,即时获取更新

  • 博主东南大学硕士在读,携程 Java 后台开发暑期实习生,利用课余时间运营一个公众号『 飞天小牛肉 』,2020/12/29 日开通,专注分享计算机基础(数据结构 + 算法 + 计算机网络 + 数据库 + 操作系统 + Linux)、Java 技术栈等相关原创技术好文。本公众号的目的就是让大家可以快速掌握重点知识,有的放矢。关注公众号第一时间获取文章更新,成长的路上我们一起进步
  • 并推荐个人维护的开源教程类项目: CS-Wiki(Gitee 推荐项目,现已累计 1.8k+ star), 致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习 ~
  • 如果各位小伙伴春招秋招没有拿得出手的项目的话,可以参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 900+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 可以获取配套教程,目前尚在更新中。

图文实例解析,InnoDB 存储引擎中行锁的三种算法的更多相关文章

  1. InnoDB存储引擎的锁

    InnoDB存储引擎的锁 锁的类型 锁的类型包括: 1.     共享锁(S lock),允许事务读取一行数据 2.     排他锁(X lock),允许事务删除或更新一行数据 锁的兼容性a X S ...

  2. InnoDB 存储引擎的锁机制

    测试环境隔离级别:REPEATABLE-READ 行级别的 - Share and Exclusive Locks 共享锁 S:允许持有S锁的事务对行进行读操作 排他锁 X: 允许持有X锁的事务对行进 ...

  3. 《Mysql技术内幕,Innodb存储引擎》——锁

    lock与latch 在数据库中lock与latch分别指不同的所. latch:可分为互斥量(mutex)和读写锁(rwlock),目的在于保证数据库内部的结构中共享资源并发时能够正确操作,其对象主 ...

  4. MySQL内核:InnoDB存储引擎 卷1

    MySQL内核:InnoDB存储引擎卷1(MySQL领域Oracle ACE专家力作,众多MySQL Oracle ACE力捧,深入MySQL数据库内核源码分析,InnoDB内核开发与优化必备宝典) ...

  5. MySQL InnoDB存储引擎中的锁机制

    1.隔离级别 Read Uncommited(RU):这种隔离级别下,事务间完全不隔离,会产生脏读,可以读取未提交的记录,实际情况下不会使用. Read Committed (RC):仅能读取到已提交 ...

  6. 《MySQL技术内幕:InnoDB存储引擎(第2版)》书摘

    MySQL技术内幕:InnoDB存储引擎(第2版) 姜承尧 第1章 MySQL体系结构和存储引擎 >> 在上述例子中使用了mysqld_safe命令来启动数据库,当然启动MySQL实例的方 ...

  7. 《MySQL技术内幕:InnoDB存储引擎》读书笔记

    一.Mysql体系结构和存储引擎 1. 概念:              数据库:物理操作系统文件或其他形式文件类型的集合.(是文件的集合,是依照某种数据模型组织起来并存放于二级存储器中的数据集合.) ...

  8. 《InnoDB存储引擎》笔记

    第1章 Mysql体系结构和存储引擎 1.1 定义数据库和实例 数据库:database,物理的操作系统文件或其他形式文件类型的集合.当使用NDB存储引擎时,数据库文件可能是存放在内存中而不是磁盘之上 ...

  9. 《MySQL技术内幕 InnoDB存储引擎 》学习笔记

    第1章  MySQL体系结构和存储引擎 1.3 MySQL存储引擎 数据库和文件系统最大的区别在于:数据库是支持事务的 InnoDB存储引擎: MySQL5.5.8之后默认的存储引擎,主要面向OLTP ...

随机推荐

  1. 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题

    大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...

  2. 『心善渊』Selenium3.0基础 — 16、Selenium对iframe表单的操作

    目录 1.什么是iframe表单 2.iframe表单操作流程 3.iframe表单操作常用方法 (1)进入表单 (2)多表单切换 4.表单操作示例 1.什么是iframe表单 实际上就是HTML页面 ...

  3. Unity3D学习笔记2——绘制一个带纹理的面

    目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...

  4. POJ 2528 Mayor's posters 贴海报 线段树 区间更新

    注意离散化!!!线段树的叶子结点代表的是一段!!! 给出下面两个简单的例子应该能体现普通离散化的缺陷: 1-10 1-4 5-10 1-10 1-4 6-10 普通离散化算出来的结果都会是2,但是第二 ...

  5. 安卓手机改造服务器——解决chroot下无法使用systemctl

    在Linux Deploy中安装的CentOS7无法使用systemctl命令,没关系我们有其他办法 写在前面 对于这个问题,我也是第一次遇见.并没有深入研究,所有如果有哪些地方有问题,欢迎指正. 问 ...

  6. Spring:Spring事务手动回滚方式

    方法1: 在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句 ...

  7. ExtJs4学习(五)最基本的Ext类

    Ext类是ExtJs中最常见.最基础的一个类,它是一个全局对象,封装了所有类.单例和 Sencha 库所提供的实用方法. 大多数用户界面组件在一个较低的层次嵌套在命名空间中, 但是提供的许多常见的实用 ...

  8. ExtJs4学习(四):Extjs 中id与itemId的区别

       为了方便表示或是指定一个组件的名称,我们通常会使用id或者itemId进行标识命名.(推荐尽量使用itemId,这样可以减少页面唯一标识而产生的冲突) id:   id是作为整个页面的Compo ...

  9. centos 安装es

    第一步:必须要有jre支持 elasticsearch是用Java实现的,跑elasticsearch必须要有jre支持,所以必须先安装jre 第二步:下载elasticsearch 进入官方下载 h ...

  10. XCTF reverse_box(idapython)

    ida先静态分析,发现有很多a2[1]不知道是什么,就远程动调了一下,发现是我们所输入的字符串,也就是我们所输入的字符串作为索引,通过v4这个数组输出,这题题目的数据漏给了,当时也是一头雾水,后面找了 ...