摘要

在这篇文章中,我将从上一篇的一个小例子开始,跟你介绍一下InnoDB中的行锁。

在这里,会涉及到一个概念:两阶段加锁协议。

之后,我会介绍行锁中的S锁和X锁,以及这两种锁的作用。

但是我们会发现仅仅有行锁是不能解决幻读问题的,于是我会用例子的方式跟你介绍各种间隙锁。

最后,我会聊一聊粒度更大的表级锁和库锁。

1 行锁

在上一篇的文章中,我们用了这个具体的例子来解释MVCC:

假设我们调换一下T5和T6:

此时,T5是没有办法执行的。

原因是这样的:InnoDB在更新一行的时候,需要先获取这一行的行锁

但是,当一条语句获取了行锁之后,不是这行语句执行完毕就能释放锁,而是要等到这个事务执行完毕,才会释放锁。

这里涉及到了两阶段加锁协议:它规定事务的加锁和解锁分为两个独立的阶段,加锁阶段只能加锁不能解锁,一旦开始解锁,则进入解锁阶段,不能再加锁。

然后我们再来说说共享锁(S锁,读锁)排他锁(X锁,写锁)

对于共享锁来说,如果一个事务获取了某一行的共享锁,则这个事务只能读这一行数据,而不能修改,并且其他事务也可以获取这一行数据的共享锁,读取这一行的数据,同样不能修改数据。

对于排它锁,只能被某一个事务获取。并且在获取排它锁之前,这一行数据上不能存在共享锁。一旦某一个事务获取了这一行的排它锁,那么只有这一个事务可以对这一行数据进行读写操作,其他事务对这一行数据的读写操作都会被阻塞。

此外,不仅仅只有更新操作,插入删除操作也会获取这一行数据的X锁。

在这里我还要再介绍这两个概念:“快照读”和“当前读”。

你可能还会有印象,在上一篇内容中,我提到了所有的更新操作都必须是“当前读”,现在可以解释原理了,在更新一行数据的时候,InnoDB会对需要更新的那行数据加上X锁,直接获取最新的那一行数据。

与之相对的是“快照读”,也就是MVCC中的数据读取方式,利用“快照”来读取数据的方式,可以极大的提高事务的并发度。

但是并不是说select语句就只能读取快照,它也照样可以给需要读取的数据加锁,来读取最新的数据。也就是说,select语句也一样可以“当前读”。

下面这两个select语句,就是分别加了读锁(S锁,共享锁)和写锁(X锁,排他锁)。

mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

注意,由于两阶段加锁协议的存在,如果你采用了一致性读,那么这个锁必须要等事务提交后才能解除。这是牺牲了并发度的一种做法。所以,如果所有的select语句,都加上了S锁,此时的“可重复读”,就变成了“序列化”。

2 间隙锁

2.1 幻读问题

还记得我们上面提到过的幻读吗?

现在你应该能够理解幻读产生的原因了:因为在插入数据的时候,InnoDB采用的是当前读,而读取数据的时候,由于MVCC的存在,采用的是快照读,这就造成了幻读。

但是我们在上面又提到了,select语句也一样可以采用“当前读”。那么,这样能解决幻读吗?

答案是能解决其中一种情况的幻读。

比如我们在上一篇文章中举的关于幻读的例子:

现在你能理解了,因为这里的select是快照读,而事务B的插入操作对于事务A来说是不可见的。如果在T5时刻,事务A的sql语句是select * from t where v = 0 for update,即采用当前读的话,是可以看得到事务B所提交的数据的,这样的话,就避免了幻读的情况。

那如果在T2时刻,事务A的语句就是select * from t where v = 0 for update会怎么样的?

如果在T2时刻就使用了“当前读”,那么T3时刻事务B是无法进行插入操作的。你可以理解为,T2时刻,InnoDB把v=0的数据,都给加上了一把锁。

因为这行sql语句v=0的数据行都锁住了,所以没有办法再插入一行v=0的数据。

这听起来似乎没什么不对的,但是你仔细想一想,InnoDB中的行锁,锁住的是已经存在的数据。而对于即将要插入的数据,为什么也会被锁住呢?这是不符合行锁的定义的。

这个时候就可以说到间隙锁了。

简单来讲,就是这条语句不仅会锁住所查询的那行数据,还会把这行数据周围的间隙锁住,不让其他事务插入。

也就是说,行锁是锁住已有的数据,而间隙锁,是锁住即将要插入的位置,不让其他数据插入。

在官方文档有这么一句话:

Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to READ COMMITTED or enable the innodb_locks_unsafe_for_binlog system variable (which is now deprecated).

也就是说,间隔锁在“可重复读”事务隔离级别是默认生效的。所以,MySQL在“可重复读”的事务隔离级别下,是有办法解决幻读问题的。

下面我们来看看哪些情况InnoDB会给数据加上间隔锁,并且这里的间隔锁范围有多大,注意,下面列举的四种情况,指的是where条件中的字段的索引类型。

  • 主键索引
  • 唯一普通索引
  • 非唯一普通索引
  • 无索引

先定义这么一个表:

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

id是主键,a是一个唯一索引,b是一个普通索引,c不包含任何的索引字段。

然后插入以下的这些数据:

insert into t values(0,0,0,0),(5,5,5,5),(10,10,10,10);

然后我们开始分析各种情况。

2.2 主键索引

因为没有其他的数据,所以主键索引在数据页内的编排如上图,并且含有4个空隙。这里说的“空隙”,指的是数据可以插入的位置

比如我要插入一个id为3的数据,这条数据就会插入到位于(0,5)这个空隙内。

下面我们开始尝试:

毫无疑问T3时刻的sql语句是会被阻塞的,原因是id = 5的这行数据已经被加锁了。那么,会不会存在有间隙锁呢?

因为这是一个主键索引,InnoDB必须保证id = 5的数据是唯一的,所以对于id=5的周围,比如(0,5)和(5,10),不需要再加间隙锁了。

那么换一个条件再试试,我们查找id大于6且id小于8的数据,此时事务B中的语句同样会被阻塞。

这是因为,在主键索引没有命中的时候,会对所在的空白范围,全部加锁。注意,我这里说的是未命中的所有空白范围,哪怕我这里的查找条件是大于6且小于8,但是加锁的范围不是(6,8),而是(5,10)。

你可以简单的理解为:从查找条件的最小值开始,往前找到第一个索引值;并且从查找条件的最大值开始,往后找到第一个索引值,这个范围就是加锁的范围。

你可能还会有一个疑问,如果是select * from t where id = 8 for update会怎么样呢?这个问题和上面一样,只要未命中,就加范围锁,锁住空隙(5,10)。

总结一下:对于主键索引来说,命中了,就只加行锁;没命中,则对查找范围的最小值往前找第一个主键,查找范围的最大值往后找第一个主键,并对这个范围加上间隙锁。

2.3 唯一索引

对于唯一索引来说,和主键索引其实是差不多的。当索引命中之后,因为唯一索引同样保证了索引的唯一性,所以不需要给这行数据的周围加上间隙锁,只会给命中的数据加锁。

但是这里和主键索引不同的地方是,在给唯一索引a = 5加锁的同时,还会回表,将a = 5对应的主键id = 5这行记录加锁。所以,事务B的修改也同样会被阻塞。

这也是为了防止造成数据不一致的情况,比如我把a = 5的这行数据删了,然后事务B又通过这行数据的主键来对这行数据进行操作。

对于带有范围的查找,和上面主键索引的间隙锁规则是一样的,这里不再赘述。值得注意的是,在唯一索引中,只要命中了,就会相应的给这条索引对应的主键id也加锁。

还需要补充一点,当主键索引和唯一索引直接命中的时候,如下图所示,InnoDB除了给a = 5这行数据加了行锁,还可能给(5, 5)这个间隙加了间隙锁,这样的说法听起来很奇怪。

因为事务A是给a = 5这行数据加了行锁,而行锁只能针对已经存在的数据,不能加到即将插入的数据上;此外,当事务A执行这条语句的时候,事务B是会被阻塞的。直到事务A提交,事务B才会提示唯一索引重复。也就是说,在事务B执行这行语句的时候,是无法访问id = 5这行数据的,事务B不知道id = 5到底存不存在

所以我才说:当索引直接命中的时候,还会加上这么一个小小的间隙锁。我没有查到这方面的资料,如果你能解释的话,请留言告诉我。

2.4 普通索引

对于普通索引来说,与唯一索引最大的区别,就是普通索引不是必须唯一的,也就是说,当插入数据的时候,可能会有重复的情况。

而在上面的内容中我们也发现了一个规律:InnoDB的间隙锁,就是为了防止新插入的数据影响查找结果。

所以对于普通索引来说,还需要防止新插入的数据和原数据一样的情况(因为唯一索引不需要担心这么一种情况)。

下面我们举例说明,在此之前先插入一行数据:

 insert into t values(8,8,5,8);

那么此时我们的索引b,是这样的:

因为是非唯一索引的原因,在两个b = 5的间隙,也能插入数据。

如图所示,我们这次把查找条件换成了b = 5。此时,我们插入的数据id = 1,理论上应该要插入(0,5)这个间隙内,但是由于间隙锁的存在,插入将被阻塞。

换一句话说,只要此时插入的数据b = 5,那么就一定无法插入。

而对于未命中的条件,规则和上文中说到的一样,根据查找条件的最小值往前找到第一个一个索引,再根据这个条件的最大值往后找到第一个索引,构成间隙锁的范围。

此外,与唯一索引一样,所有命中的数据行,都会回表将主键id也锁住。

2.5 无索引

可以看到,我们的查找条件是c = 5,直接命中了数据。此时我们插入的数据是c = 6,看起来和事务A无关,但是出乎意料的是,事务B还是会被阻塞

直接说结论:对于不含有索引的查找项来说,会锁住所有的间隙和所有的数据。

关于幻读的问题的一些case,到这里就研究完了(但是我不确定有没有遗漏,如果有,还请你留言告诉我)。

在最后还需要说一个概念,行锁与间隔锁,合称next-key lock。并且需要注意的是,只有在可重复读的事务隔离级别中,才会有间隔锁。并且可重复读是遵循两阶段锁协议,所有加锁的资源,都是在事务提交或者回滚的时候才释放的。所以,在防止幻读产生的时候,同样降低了并发度。

3 表级锁

在上一节说完了行级锁之后,我们再来聊聊表级锁。

表级锁有两种,一种是显式添加的,一种是隐式添加的。

3.1 读写表锁

还记得我们在上文中提到的读锁和写锁的特点吗,这点在表锁中是一样的。

给表加上了写锁,意味着只有这个会话拥有读写这个表的权限;给表加上了读锁,才能读取这个表上的数据,并且可以多个线程共享读锁,但是,只有当某个表上没有读锁时,才能给这个表加上写锁。

下面是给表加锁的语法:

lock tables table_name read
lock tables table_name write

3.2 MDL

MDL指的是(Metadata Lock),指的是元数据锁。

MDL也分为了读锁和写锁,功能和上面提到的一样。

只不过MDL不需要像表锁那样显式的使用,它会在访问一个表的时候会被自动加上。其中,在某个表对数据进行操作(包括insert,delete,update,select)的时候,会隐式的加上MDL读锁,在修改表的结构的时候,会加上写锁

这样做的目的是,防止在一个事务操作数据的时候,表结构被另一个事务给修改了。或者在某一个事务修改表结构的时候,不允许其他的事务操作数据。

4 库锁

顾名思义,库锁就是对整个数据库实例加锁。

MySQL提供了一个加全局读锁的方法,命令是Flush tables with read lock (FTWRL)

使用过这个命令之后,相当于对全库增加了一个读锁,此时其他线程的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句都会被阻塞。

全局锁的典型使用场景是,做全库逻辑备份。当然了,实现这个功能,我们也可以使用“可重复读”的事务隔离级别,做一次快照读,依然可以实现备份的功能。只不过,有些引擎并没有实现这个事务隔离级别。

写在最后

首先,谢谢你能看到这里。

在这篇文章中,尤其是间隙锁部分的内容,我没有查到太多的资料,所以很多内容都是我自己的理解。所以如果你发现了一些bad case,请你留言告诉我。又或者你发现了我哪里的理解是不对的,也请你留言告诉我,谢谢!

当然了,如果有哪里是我讲的不够明白的,也欢迎留言交流~

PS:如果有其他的问题,也可以在公众号找到我,欢迎来找我玩~

MySQL 入门(4):锁的更多相关文章

  1. MySQL入门笔记

    MySQL入门笔记 版本选择: 5.x.20 以上版本比较稳定 一.MySQL的三种安装方式: 安装MySQL的方式常见的有三种: ·          rpm包形式 ·          通用二进制 ...

  2. mysql入门与进阶

    MySQL入门与进阶 需求:对一张表中的数据进行增删改查操作(CURD) C:create 创建 U:update 修改 R:read 读|检索 查询 D:delete 删除涉及技术:数据库 1.数据 ...

  3. Mysql高手系列 - 第26篇:聊聊如何使用mysql实现分布式锁

    Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 这是Mysql系列第26篇. 本篇我们使用my ...

  4. MySQL 入门(5):复制

    摘要 在这篇文章中,我将从MySQL为什么需要主从复制开始讲起,然后会提到MySQL复制的前提,bin log. 在这里会说明三种格式的bin log分别会有什么优缺点. 随后会讲到主从延迟方面的问题 ...

  5. MySQL入门(引擎、数据类型、约束)

    MySQL入门(二) 表的引擎:驱动数据的方式 - 数据库优化 # 概要:引擎是建表规定的,提供给表使用,不是数据库的 # 展示所有引擎 show engines; # innodb(默认): 支持事 ...

  6. MySQL入门(7)——表数据的增、删、改

    MySQL入门(7)--表数据的增.删.改 插入数据 使用INSERT···VALUES语句插入数据 INSERT语句最常用的格式是INSERT···VALUES: INSERT [LOW_PRIOR ...

  7. mysql 行级锁的使用以及死锁的预防

    一.前言 mysql的InnoDB,支持事务和行级锁,可以使用行锁来处理用户提现等业务.使用mysql锁的时候有时候会出现死锁,要做好死锁的预防. 二.MySQL行级锁 行级锁又分共享锁和排他锁. 共 ...

  8. 21分钟 MySQL 入门教程(转载!!!)

    21分钟 MySQL 入门教程 目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数 ...

  9. MySQL入门02-MySQL二进制版本快速部署

    在上篇文章 MySQL入门01-MySQL源码安装 中,我们介绍了MySQL源码安装的方法. 源码安装虽然有着更加灵活和更加优化等诸多优势.但源码编译安装部署的过程相对复杂,而且整个过程所花费的时间很 ...

  10. MYSQL入门全套(第三部)

    MYSQL入门全套(第一部) MYSQL入门全套(第二部) 索引简介 索引是对数据库表中一个或多个列(例如,employee 表的姓名 (name) 列)的值进行排序的结构.如果想按特定职员的姓来查找 ...

随机推荐

  1. Python程序设计实验报告四:循环结构程序设计(设计型实验)

    安徽工程大学 Python程序设计 实验报告 班级   物流191   姓名  姚彩琴  学号3190505129 成绩 日期     2020.4.8     指导老师       修宇 [实验名称 ...

  2. A - Oil Deposits DFS

    The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSu ...

  3. LCA Nearest Common Ancestors (很典型的例题)

    A rooted tree is a well-known data structure in computer science and engineering. An example is show ...

  4. 数据挖掘入门系列教程(九)之基于sklearn的SVM使用

    目录 介绍 基于SVM对MINIST数据集进行分类 使用SVM SVM分析垃圾邮件 加载数据集 分词 构建词云 构建数据集 进行训练 交叉验证 炼丹术 总结 参考 介绍 在上一篇博客:数据挖掘入门系列 ...

  5. Maven 命令深度理解

    1.前言 Maven 命令看起来简单,一学即会 .其实,Maven 命令底层是插件的执行过程.了解插件和插件目标才有助于深刻的理解 Maven命令. 2.插件与命令的关系 Maven本质上是一个插件框 ...

  6. Laravel joinSub 子查询的写法

    $subQuery = $model::query() ->from('table1 as a') ->getQuery(); $query = $model::query() -> ...

  7. MySQL使用mysqldump+binlog完整恢复被删除的数据库

    (一)概述 在日常的MySQL数据库运维过程中,可能会遇到用户误删除数据,常见的误删除数据操作有: 用户执行delete,因为条件不对,删除了不应该删除的数据(DML操作): 用户执行update,因 ...

  8. redis:String字符串类型(三)

    字符串拼接(如果key不存在则创建):append name " applesnt" 获取字符串的长度:strlen name 127.0.0.1:6379> set nam ...

  9. .NET Core 初识

    什么是 ASP.NET Core? ASP.NET Core 是一个新的开源和跨平台的框架,用于构建如 Web 应用.物联网(IoT)应用和移动后端应用等连接到互联网的基于云的现代应用程序.ASP.N ...

  10. 大数据MapReduce相关的运维题

    1.在集群节点中/usr/hdp/2.4.3.0-227/hadoop-mapreduce/目录下,存在一个案例 JAR 包 hadoop-mapreduce-examples.jar.运行 JAR ...