MySQL 不同隔离级别,都使用了什么锁?
大家好,我是树哥。
在上篇文章,我们聊了「MySQL 啥时候会用表锁,啥时候用行锁」这个问题。在文章中,我们还留了一个问题,即:如果查询或更新时的数据特别多,是否从行锁会升级为表锁?此外,还有朋友留言说到:不同的隔离级别可能会用不同的锁,可以结合隔离级别来聊聊。
其实上面虽然是两个问题,但如果你把不同隔离级别下的加锁问题搞清楚了,那么第一个问题自然也清楚了。今天,就让我带着大家来聊聊不同隔离级别下,都会使用什么锁!
说透 MySQL 锁机制
在深入探讨不同隔离级别的锁内容之前,我们需要先回顾一下关于 MySQL 锁的本质以及一些基础内容,这样有利于我们后续的理解。
对于 MySQL 来说,如果只支持串行访问的话,那么其效率会非常低。因此,为了提高数据库的运行效率,MySQL 需要支持并发访问。而在并发访问的情况下,会发生各种各样的问题,例如:脏读、不可重复读、幻读等问题。为了解决这些问题,就出现了事务隔离级别。
本质上,事务隔离级别就是为了解决并发访问下的数据一致性问题的。不同的事务隔离级别,解决了不同程度的数据一致性。而我们所说的全局锁、表锁、行级锁等等,其实都是事务隔离级别的具体实现。而 MVCC、意向锁,则是一些局部的性能优化。
上面这段话,基本上就是对 MySQL 锁机制很透彻的理解。当我们懂了这些概念之间的关系之后,我们才能更加清晰地理解知识点。
事务隔离级别
相信大家都知道,MySQL 的事务隔离级别有如下 4 个,分别是:
- 读未提交
- 读已提交(READ COMMITTED)
- 可重复读(REPEATABLE READ)
- 串行化
读未提交,可以读取到其他事务还没提交的数据。 在这个隔离级别下,由于可以读取到未提交的值,因此会产生「脏读」问题。举个例子:A 事务更新了 price 为 30,但还未提交。此时 B 事务读取到了 price 为 30,但后续 A 事务回滚了,那么 B 事务读取到的 price 就是错的(脏的)。
读已提交,只能读到其他事务已经提交的数据。 这个隔离级别解决了脏读的问题,不会读到未提交的值,但是却会产生「不可重复读」问题。「不可重复读」指的是在同一个事务范围内,前后两次读取到的数据不一样。举个例子:A 事务第 1 次读取了 price 为 10。随后 B 事务将 price 更新为 20,接着 A 事务再次读取 price 为 30。A 事务前后两次读取到的数据是不一样的,这就是不可重复读。
思考题:MySQL 读已提交可以解决脏读问题,那它具体是如何解决的?
可重复读,指的是同一事务范围内读取到的数据是一致的。 这个隔离级别解决了「不可重复读」的问题,只要是在同一事务范围内,那么读取到的数据就是一样的。对于 MySQL Innodb 来说,其实通过 MVCC 来实现的。但「可重复读」隔离级别会产生幻读问题,即对于某个范围的数据读取,前后两次可能读取到不同的结果。
举个例子:数据库中有 price 为 1、3、5 三个商品,此时 A 事务查询 price < 10 的商品,查询到了 3 个商品。随后 B 事务插入了一条 price 为 7 的商品。接着 A 事务继续查询 price < 10 的商品,这次却查询到了 4 个商品。
可以看到「幻读」与「不可重复读」是有些类似的,只是「不可重复读」更多指的是某一条记录,而「幻读」指的则是某个范围数据。对于 MySQL Innodb 来说,其通过行级锁级别的 Gap Lock 解决了幻读的问题。
串行化,指的是所有事务串行执行。 这个就最简单了,不用去竞争,一个个去执行,但是效率也是最低的。
MySQL 锁类型
在 MySQL 中有全局锁、表级锁、行级锁三种类型,其中比较关键的是表级锁盒行级锁。
对于表级锁而言,其又分为表锁、元数据锁、意向锁三种。对于元数据锁而言,基本上都是数据库自行操作,我们无须关心。在 Innodb 存储存储引擎中,表锁也用得比较少。
对于行级锁而言,其又记录锁、间隙锁、Next-Key 锁。记录锁就是某个索引记录的锁,间隙锁就是两个索引记录之间的空隙锁,Next-Key 则是前面两者的结合。
在 Innodb 存储引擎中,我们可以通过下面的命令来查询锁的情况。
// 开启锁的日志
set global innodb_status_output_locks=on;
// 查看innodb引擎的信息(包含锁的信息)
show engine innodb status\G;
查询结果一般如下图所示:
上面几种不同类型的锁,其各自的关键字为:
- 表级的意向排它锁(IX):lock mode IX。
- 表级的插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention
- 行级的记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap
- 行级的间隙锁(LOCK_GAP): lock_mode X locks gap before rec
- 行级的 Next-key 锁(LOCK_ORNIDARY): lock_mode X
通过上面的命令,我们就可以知道不同的事务隔离级别使用了哪些锁了。
接下来,我们一个个来看看:不同事务隔离级别,都使用了哪些锁来实现。
读未提交
首先,我们创建一个 price_test 表并插入一些测试数据。
// 创建 price_test 表
CREATE TABLE `test`.`price_test` (
`id` BIGINT(64) NOT NULL AUTO_INCREMENT,
`name` varchar(32) not null,
`price` INTEGER(4) NULL,
PRIMARY KEY (`id`));
// 插入测试数据
INSERT INTO price_test(name,price) values('apple', 10);
接着,我们打开两个命令行窗口,并且都修改事务隔离级别为「读未提交」。
// 设置隔离级别
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
// 查看隔离级别
select @@transaction_isolation;
接着,事务 A 执行如下命令,查询出 id 为 1 记录的 price 值。
// 执行命令
beign;
select * from price_test where id = 1;
// 执行结果
+----+-------+-------+
| id | name | price |
+----+-------+-------+
| 1 | apple | 10 |
+----+-------+-------+
1 row in set (0.00 sec)
接着,事务 B 执行如下命令,修改 price 为 20。
begin;
update price_test set price = 20 where id = 1;
接着,事务 A 再次读取 id 为 1 记录的 price 值。
select * from price_test where id = 1;
从下图可以看到,事务 A 读取到了事务 B 未提交的数据,这其实就是脏读了。
从这个例子,我们可以得出一些结论:在「读未提交」事务隔离级别下,读写是可以同时进行的,不会阻塞。
看到这里,我突然想到了一个问题:那么写写是否会阻塞阻塞呢?
接下来,我们继续做一个测试:事务 A 和 事务 B 同时对 id 为 1 的记录进行更新,看看是否能够更新成功。
如上图所示,我先用如下命令在事务 A(上边的窗口)执行,将 price 修改为 15。
begin;
update price_test set price = 15 where id = 1;
结果执行成功了,但此时事务 A 还未提交。
接着,我先用如下命令在事务 B(下边的窗口)执行,将 price 修改为 20。
从图中可以看到,事务 B 阻塞卡住了。
从这个例子,我们可以得出结论:在「读未提交」事务隔离级别下,写写不可以同时进行的,会阻塞。
此时,我们通过查看锁信息可以看到,其是加上一个行级别的记录锁,如下图所示。
当我使用 rollback 命令回滚事务 A 之后,事务 B 立刻就执行了,并且事务 A 还读取到了事务 B 设置的值,如下图所示。
有些小伙伴会说:如果指定了非索引的列作为查询条件,是否会触发间隙锁呢?
接下来我们测试一下。
我们往 price_test 表再插入一条数据,此时数据库中的数据如下所示。
接着,我们在事务 A 执行如下命令,查询 price > 15 的记录。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from price_test where price > 15 for update;
+----+--------+-------+
| id | name | price |
+----+--------+-------+
| 2 | orange | 30 |
+----+--------+-------+
1 row in set (0.00 sec)
接着,我们在事务 B 执行如下命令,查询 price > 5 的记录。
begin;
select * from price_test where price > 5 for update;
从如下结果可以看到,事务 B 阻塞住了。
此时我们在事务 A 查看锁的情况,如下图所示。
从上图可以看出,MySQL 只是加上了一个记录锁,并没有加间隙锁。
最后我们总结一下:在「读未提交」隔离级别下,读写操作可以同时进行,但写写操作无法同时进行。与此同时,该隔离级别下只会使用行级别的记录锁,并不会用间隙锁。
读已提交
在「读已提交」隔离级别下,我们按之前的方式进行测试。
首先,我们设置一下隔离级别为「读已提交」。
// 设置隔离级别
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED;
// 查看隔离级别
select @@transaction_isolation;
接着,我们测试同时对 id 为 1 的数据进行更新,看看会发生什么。
事务 A 执行如下命令:
begin;
update price_test set price = 15 where id = 1;
事务 B 执行如下命令
begin;
update price_test set price = 20 where id = 1;
事务 B 阻塞了。查看下锁信息,如下图所示。
可以看到,其锁是一个行级别的记录锁,结果和「读未提交」的是一样的。
接下来,我们继续看看范围的查询是否会触发间隙锁。
事务 A 执行:
begin;
select * from price_test where price > 5 for update;
事务 B 执行:
begin;
select * from price_test where price > 15 for update;
事务 B 会阻塞,查看锁信息如下图所示。
可以看到,还是只有一个行级别的记录锁,并没有间隙锁。
看到这里,你会发现「读已提交」和「读未提交」非常相似。那么它们具体有啥区别呢?
其实他们的最大区别,就是「读已提交」解决了脏读的问题。
可重复读
在「可重复读」隔离级别下,我们按之前的方式进行测试。
首先,我们设置一下隔离级别为「可重复读」。
// 设置隔离级别
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
// 查看隔离级别
select @@transaction_isolation;
接着,我们测试同时对 id 为 1 的数据进行更新,看看会发生什么。
事务 A 执行如下命令:
begin;
update price_test set price = 15 where id = 1;
事务 B 执行如下命令
begin;
update price_test set price = 20 where id = 1;
事务 B 阻塞了。查看下锁信息,毫无疑问,其实这里还是只会有记录锁,因为指定了索引。
接下来,我们继续看看范围的查询是否会触发间隙锁。
事务 A 执行:
begin;
select * from price_test where price > 5 for update;
事务 B 执行:
begin;
select * from price_test where price > 15 for update;
事务 B 会阻塞,查看锁信息如下图所示。
可以看到,在这里就变成了 Next-Key 锁,就是记录锁和间隙锁结合体。
总结一下:在「可重复读」隔离级别下,使用了记录锁、间隙锁、Next-Key 锁三种类型的锁。
值得一提的是,我们前面说过:可重复读存在幻读的问题,但实际上在 MySQL 中,因为其使用了间隙锁,所以在「可重复读」隔离级别下,可以通过加 锁解决幻读问题。因此,MySQL 将「可重复读」作为了其默认的隔离级别。
总结
看到这里,我想我们可以对文章开头提出的问题做个解答了:MySQL 不同隔离级别,都使用了什么样的锁?
对于任何隔离级别,表级别的表锁、元数据锁、意向锁都是会使用的,但对于行级别的锁则会有些许差别。
在「读未提交」和「读已提交」隔离级别下,都只会使用记录锁,不会用间隙锁,当然也不会有 Next-Key 锁了。
而对于「可重复读」隔离级别来说,会使用记录锁、间隙锁和 Next-Key 锁。
今天我们是从隔离级别这个角度来看锁的应用,但什么时候会用上记录锁?什么时候会用上间隙锁?后面有机会,我们将聊聊这部分的问题。
如果你喜欢今天的文章,那么请一键三连支持我哦!
参考资料
- MySQL 进阶系列:不同隔离级别下加锁情况 - 墨天轮
- 写得貌似不错,学习一波!VIP!锁是理解隔离级别的钥匙 - 腾讯云开发者社区 - 腾讯云
- VIP!写得不错!深入了解 mysql--gap locks,Next-Key Locks_一撸向北的博客 - CSDN 博客_gap 锁和 next key 区别
- 求你了,别再说数据库的锁,锁的只是索引了 - 文章详情
MySQL 不同隔离级别,都使用了什么锁?的更多相关文章
- MySQL事务隔离级别初探
MySQL有四种隔离级别,分别是: READ UNCOMMITTED(未提交读) READ COMMITTED(提交读) REPEATABLE READ (可重复读) SERIALIZABLE(可串行 ...
- mysql事务隔离级别和MVCC
一.三种问题: 脏读(Drity Read):事务A更新记录但未提交,事务B查询出A未提交记录. 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次 ...
- Mysql得隔离级别
一.首先什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事 ...
- Mysql的隔离级别
一.首先什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事 ...
- [51CTO]新说MySQL事务隔离级别!
新说MySQL事务隔离级别! 事务隔离级别这个问题,无论是校招还是社招,面试官都爱问!然而目前网上很多文章,说句实在话啊,我看了后我都怀疑作者弄懂没!本文所讲大部分内容,皆有官网作为佐证,因此对本文内 ...
- 查询mysql事务隔离级别
查询mysql事务隔离级别 查询mysql事务隔离级别 分类: DB2011-11-26 13:12 2517人阅读 评论(0) 收藏 举报 mysqlsessionjava 1.查看当前会话隔离 ...
- mysql 事务隔离级别详解
事物的 隔离级别,说简单非常简单(新手也能说出 是个隔离级别 和 影响),说男也非常难.(很多 有几年编程 经验的程序员依旧搞不清楚) 废话不多少 直接开始: 事务的隔离级别 是用来描述 事务的读关系 ...
- MySql的隔离级别总结
使用MySql也有一段时间了,但是很多MySql相关或者说是数据库相关的知识还是一知半解,最近在学hibernate这个框架时碰到挺多和数据库相关的知识盲区,所以下面根据自己对MySql系统相关知识消 ...
- mysql数据库隔离级别
# 原创,转载请留言联系 事务的隔离级别 (由高到低)1.串行化(serializable):一个事务一个事务的执行2.可重复读(Repeatable-Read) 可重复读,无论其他事务是否修改并提交 ...
随机推荐
- TypeScript ReadonlyArray(只读数组类型) 详细介绍
1.ReadonlyArray 简介 在TypeScript中,除了Array<T>类型,还有一个ReadonlyArray<T>类型,ReadonlyArray类型和Arra ...
- Linux for CentOS 下的 keepalived 安装与卸载以及相关命令操作之详细教程
百度百科解释: keepalived 是一个类似于 layer3, 4 & 7 交换机制的软件,也就是我们平时说的第 3 层.第 4 层和第 7 层交换.Keepalived 的作用是检测 w ...
- Contest
Contest 题目 链接 题目描述 \(n\) 支队伍一共参加了三场比赛. 一支队伍 \(x\) 认为自己比另一支队伍 \(y\) 强当且仅当 \(x\) 在至少一场比赛中比 \(y\) 的排名高. ...
- 【Unity基础知识】认识常用的生命周期函数(Awake、Start、Update...)
一.了解帧的概念 游戏的本质就是一个死循环 每一次循环都会处理游戏逻辑 并 更新一次游戏画面 之所以能看到画面在动 是因为 切换画面速度达到一定速度时 人眼就会认为画面是动态且流畅的 一帧就是执行了一 ...
- 【填坑】树莓派4B上运行Bullseye版本系统,不能登录xrdp的问题~~
以前使用 buster,安装xrdp后 pi用户xrdp登录正常, 可自从使用了 bullseye系统,pi登录xrdp后,出现黑屏不能登录现象. 网上搜寻解决方案,一种方法是: 登录树莓派后,打开这 ...
- kube-scheduler的调度上下文
前一章节了解到了kube-scheduler中的概念,该章节则对调度上下文的源码进行分析 Scheduler Scheduler 是整个 kube-scheduler 的一个 structure,提供 ...
- qbxt五一数学Day2
目录 1. 判断素数(素性测试) 1. \(O(\sqrt n)\) 试除 2. Miller-Rabin 素性测试 * 欧拉函数 2. 逆元 3. exgcd(扩展欧几里得) 4. 离散对数(BSG ...
- springboot的@ConditionalOnClass注解
大家好,我是"良工说技术". 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? ...
- SkiaSharp 之 WPF 自绘 投篮小游戏(案例版)
此案例主要是针对光线投影法碰撞检测功能的示例,顺便做成了一个小游戏,很简单,但是,效果却很不错. 投篮小游戏 规则,点击投篮目标点,就会有一个球沿着相关抛物线,然后,判断是否进入篮子里,其实就是一个矩 ...
- Regular采样类定义和测试
这个算法是均匀采样算法,继承于Sampler类. 类声明: #pragma once #ifndef __REGULAR_HEADER__ #define __REGULAR_HEADER__ #in ...