美团二面: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 锁,相当于把整个表锁住了,这是挺严重的问题。

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

完!

<think>嗯,用户问的是MySQL记录锁间隙锁能否防止删除操作导致。这个问题需要结合InnoDB的锁机制来分析。 用户可能正在学习MySQL的锁机制,或者在实际开发中遇到了问题。从提问的专业性来看,用户应该有一定数据库基础,但可能对锁的具体工作方式不太清楚。需要解释清楚next-key lock的组成和工作原理。 参考引用内容提到,针对当前问题是通过next-key lock解决的。但用户特别关注"删除操作"这个场景,这需要更深入的分析。引用[3]提到锁模式识别方法,这对验证锁类型很有帮助。 首先需要明确的定义:同一事务内多次查询时,由于其他事务的删除或插入操作导致结果集不一致。引用[1]和[2]都说明next-key lock可以防止插入导致,但删除操作的情况需要另作讨论。 关键点在于:当执行删除操作时,如果事务A删除某条记录,事务B随后查询,可能出现事务B看不到被删除记录的情况(因为已提交)。但如果在事务A删除时加上next-key lock,就能阻塞其他事务的删除操作,从而防止。 具体来说:next-key lock由记录锁间隙锁组成。记录锁锁定索引记录,间隙锁锁定索引之间的范围。在可重复隔离级别下,执行删除操作时会对删除的记录加上next-key lock,这样其他事务就无法插入或删除该范围内的记录。 不过有个例外情况:如果删除的是范围内最后一条记录,间隙锁可能无法完全阻止其他事务插入新记录。但通常next-key lock机制能有效防止删除导致。 用户可能还想知道如何验证锁的效果,可以补充说明:通过SHOW ENGINE INNODB STATUS命令查看锁信息,观察LOCK_MODE字段是否为X(表示next-key lock)。</think>在 MySQL 的 InnoDB 引擎中,**记录锁(Record Lock)和间隙锁(Gap Lock)组成的 next-key lock 可以有效防止删除操作导致**,但需要满足特定条件(在可重复隔离级别下执行当前操作)。以下是详细分析: --- ### 一、核心机制解析 1. **的定义** 同一事务内多次查询时,**其他事务的删除或插入操作**导致结果集不一致(例如:事务 A 第一次查询有 5 条记录,事务 B 删除其中 1 条后,事务 A 第二次查询仍看到 5 条)。 2. **锁的作用** - **记录锁 (Record Lock)**:锁定索引记录(例如锁定 `id=5` 的行)。 - **间隙锁 (Gap Lock)**:锁定索引之间的范围(例如锁定 `id >3 AND id <7` 的间隙)。 - **Next-Key Lock**:**记录锁 + 间隙锁的组合**(例如锁定 `(3,7]` 的范围,包含间隙和记录)。 3. **防止删除导致** - 当执行 `DELETE` 或 `SELECT ... FOR UPDATE`(当前)时,InnoDB 会对涉及的数据加 **next-key lock**[^2][^3]。 - **若事务 A 删除某条记录**(例如 `id=5`),它会锁定 `id=5` 的记录及其相邻间隙(如 `(3,7]`)。 - **此时事务 B 尝试删除同一范围内的记录**(例如 `id=6`)会被阻塞,直到事务 A 提交。 - 因此,事务 A 的两次查询结果集不会因其他事务的删除而改变,**避免了**。 --- ### 二、关键实验验证 假设表 `t` 的数据如下: | id (主键) | |-----------| | 3 | | 5 | | 7 | **操作顺序**: 1. 事务 A 执行: ```sql SELECT * FROM t WHERE id > 3 FOR UPDATE; -- 对 (3,5], (5,7], (7,+∞) 加 next-key lock ``` 2. 事务 B 尝试删除 `id=5`: ```sql DELETE FROM t WHERE id = 5; -- 被阻塞!因为事务 A 的间隙锁覆盖了 (3,7] ``` 3. 事务 A 再次查询: ```sql SELECT * FROM t WHERE id > 3; -- 结果仍为 id=5,7(无) ``` **结论**: 由于事务 B 的删除操作被 next-key lock 阻塞,事务 A 的两次查询结果一致,**防止**[^1][^2]。 --- ### 三、注意事项 1. **隔离级别要求**: 仅当使用 **可重复(RR)隔离级别** 时,InnoDB 才会启用 next-key lock 机制[^2]。 2. **操作类型要求**: 仅 **当前**(`SELECT ... FOR UPDATE`、`DELETE`、`UPDATE`)会触发 next-key lock;快照(普通 `SELECT`)通过 MVCC 避免[^2]。 3. **索引依赖**: 若删除条件未命中索引,InnoDB 会退化为锁全表,效率降低但仍可防止。 --- ### 总结 | 场景 | 能否防止 | 原因 | |------------------------------|--------------|--------------------------| | **删除操作 + 当前 + RR 隔离级别** | ✅ | next-key lock 阻塞其他事务的删除/插入 | | 普通 SELECT(快照) | ✅ | 依赖 MVCC 机制 | | 已提交(RC)隔离级别 | ❌ | 不启用间隙锁 | > 因此,在可重复隔离级别下,**通过 next-key lock(记录锁+间隙锁),MySQL 能够有效防止删除操作导致问题**[^1][^2][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值