news 2026/5/4 22:23:03

MySQL记录锁+间隙锁可以防止删除操作而导致的幻读吗?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL记录锁+间隙锁可以防止删除操作而导致的幻读吗?

昨天有位读者在美团二面的时候,被问到关于幻读的问题:

面试官反问的大概意思是,MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读吗?

答案是可以的。

接下来,通过几个小实验来证明这个结论吧,顺便再帮大家复习一下记录锁+间隙锁。

什么是幻读?

首先来看看 MySQL 文档是怎么定义幻读(Phantom Read)的:

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times.

翻译:当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。

举个例子,假设一个事务在 T1 时刻和 T2 时刻分别执行了下面查询语句,途中没有执行其他任何语句:

SELECT * FROM t_test WHERE id > 100;

只要 T1 和 T2 时刻执行产生的结果集是不相同的,那就发生了幻读的问题,比如:

  • T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 6 条行记录,那就发生了幻读的问题。
  • T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 4 条行记录,也是发生了幻读的问题。

MySQL 是怎么解决幻读的?

MySQL 可重复读隔离级别是解决幻读问题,查询数据的操作有两种方式,所以解决的方式是不同的:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

实验验证

接下来,来验证「 MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读问题」的结论。

实验环境:MySQL 8.0 版本,可重复读隔离级。

现在有一张用户表(t_user),表里只有一个主键索引,表里有以下行数据:

现在有一个 A 事务执行了一条查询语句,查询到年龄大于 20 岁的用户共有 6 条行记录。

然后, B 事务执行了一条删除 id = 2 的语句:

此时,B 事务的删除语句就陷入了等待状态,说明是无法进行删除的。

因此,MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读问题。

加锁分析

问题来了,A 事务在执行 select ... for update 语句时,具体加了什么锁呢?

我们可以通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

输出的内容很多,共有 11 行信息,我删减了一些不重要的信息:

从上面输出的信息可以看到,共加了两种不同粒度的锁,分别是:

  • 表锁(LOCK_TYPE: TABLE):X 类型的意向锁;
  • 行锁(LOCK_TYPE: RECORD):X 类型的 next-key 锁;

这里我们重点关注「行锁」,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思:

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;
  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;
  • 如果 LOCK_MODE 为 X, GAP,说明是间隙锁;

然后通过 LOCK_DATA 信息,可以确认 next-key 锁的范围,具体怎么确定呢?

  • 根据我的经验,如果 LOCK_MODE 是 next-key 锁或者间隙锁,那么LOCK_DATA 就表示锁的范围最右值,而锁范围的最左值为 LOCK_DATA 的上一条记录的值。

因此,此时事务 A 在主键索引(INDEX_NAME : PRIMARY)上加了 10 个 next-key 锁,如下:

  • X 型的 next-key 锁,范围:(-∞, 1]
  • X 型的 next-key 锁,范围:(1, 2]
  • X 型的 next-key 锁,范围:(2, 3]
  • X 型的 next-key 锁,范围:(3, 4]
  • X 型的 next-key 锁,范围:(4, 5]
  • X 型的 next-key 锁,范围:(5, 6]
  • X 型的 next-key 锁,范围:(6, 7]
  • X 型的 next-key 锁,范围:(7, 8]
  • X 型的 next-key 锁,范围:(8, 9]
  • X 型的 next-key 锁,范围:(9, +∞]

这相当于把整个表给锁住了,其他事务在对该表进行增、删、改操作的时候都会被阻塞

只有在事务 A 提交了事务,事务 A 执行过程中产生的锁才会被释放。

为什么只是查询年龄 20 岁以上行记录,而把整个表给锁住了呢?

这是因为事务 A 的这条查询语句是全表扫描,锁是在遍历索引的时候加上的,并不是针对输出的结果加锁

因此,在线上在执行 update、delete、select ... for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了,这是挺严重的问题。

如果对 age 建立索引,事务 A 这条查询会加什么锁呢?

接下来,我对 age 字段建立索引,然后再执行这条查询语句:

接下来,继续通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

具体的信息,我就不打印了,我直接说结论吧。

因为表中有两个索引,分别是主键索引和 age 索引,所以会分别对这两个索引加锁。

主键索引会加如下的锁:

  • X 型的记录锁,锁住 id = 2 的记录;
  • X 型的记录锁,锁住 id = 3 的记录;
  • X 型的记录锁,锁住 id = 5 的记录;
  • X 型的记录锁,锁住 id = 6 的记录;
  • X 型的记录锁,锁住 id = 7 的记录;
  • X 型的记录锁,锁住 id = 8 的记录;

分析 age 索引加锁的范围时,要先对 age 字段进行排序。

age 索引加的锁:

  • X 型的 next-key lock,锁住 age 范围 (19, 21] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (21, 21] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (21, 23] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (23, 23] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (23, 39] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (39, 43] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (43, +∞] 的记录;

化简一下,age 索引 next-key 锁的范围是 (19, +∞]。

可以看到,对 age 字段建立了索引后,查询语句是索引查询,并不会全表扫描,因此不会把整张表给锁住

总结一下,在对 age 字段建立索引后,事务 A 在执行下面这条查询语句后,主键索引和 age 索引会加下图中的锁。

事务 A 加上锁后,事务 B、C、D、E 在执行以下语句都会被阻塞。

总结

在 MySQL 的可重复读隔离级别下,针对「当前读」的查询语句会对索引加记录锁+间隙锁,这样可以避免其他事务执行「增、删、改」时导致幻读的现象。

有一点要注意的是,在执行 update、delete、select ... for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了,这是挺严重的问题。

这次教了大家如何分析事务具体加了什么锁,以后大家可以多做实验,然后自己尝试分析分析,掌握分析的方法,远比记住加锁规则强!

完!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 18:52:48

Elcomsoft 系统取证工具: 选择正确策略, 冷启动取证 vs 实时系统分析

调查的第一步往往并不简单。你是应该关闭系统并对存储介质制作镜像,选择安全但缓慢的传统路径?还是应该在运行中的系统上使用应急响应工具来获取密码和密钥,或者重新启动进入一个干净的取证环境?传统的智慧可能建议直接断电以保护…

作者头像 李华
网站建设 2026/5/2 20:33:16

Java面试实战:从基础到微服务开发中的关键技术点

Java面试实战:从基础到微服务开发中的关键技术点 场景描述 小白程序员“超好吃”正在参加某互联网大厂的Java开发岗面试,面试官通过一系列循序渐进的问题考察其技术能力。场景聚焦在电商场景中的微服务架构设计与缓存技术应用。第一轮:基础与…

作者头像 李华
网站建设 2026/5/3 8:53:06

如何轻松将 Outlook 联系人导出到 Excel

如果您想将联系人从 Microsoft Outlook 导出到 Excel 电子表格以进行备份、迁移、邮件列表、数据分析或其他用途,本指南可以为您提供有效的方法。它通过 5 种方式解释了如何将联系人从 Outlook 导出到 Excel,您可以按照正确的步骤完成 Outlook 联系人导出…

作者头像 李华
网站建设 2026/5/2 16:26:44

基于STM32简易跟随便携物品车的设计

基于STM32简易跟随便携物品车的设计 第一章 系统整体设计 基于STM32的简易跟随便携物品车以“低成本、易实现、高适配”为核心设计目标,面向短途出行场景,实现对用户的实时跟随、避障及便携收纳。系统整体采用“感知-决策-执行”的闭环控制架构&#xff…

作者头像 李华
网站建设 2026/5/1 0:25:57

安路TD新建FPGA工程

启动 TD 软件,创建一个新的空的工程 Project Name:新建一个 fpga 工程名 Project Path:工程路径设置, 这里我们需要根据自己的磁盘和文件管理路径合理设置 Device Family:设置器件的系列, 这里设置 DR1 Device Name:设置芯片的名字, 这里选择 DR1M90GEG484 一个空的 FPG…

作者头像 李华