登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 事务隔离级别下间隙锁影响范围的分析方法

来源:17golang原创

时间:2026-09-23 13:12:11 390浏览 收藏

MySQL InnoDB 里看到“插入被卡住”,先不要直接判断成表锁。更常见的原因是事务隔离级别、索引条件和扫描范围共同决定了间隙锁覆盖的区间。分析时按“隔离级别 → 实际使用的索引 → 区间前后的索引记录 → 锁等待方”四步还原,通常就能解释阻塞点。

官方参考:https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html

要点速览
  • InnoDB 默认是 REPEATABLE READ,范围型锁定读会使用间隙锁或临键锁防止范围内插入。
  • 唯一索引的完整等值条件通常只锁命中的索引记录;非唯一条件或范围扫描要看扫描到的索引区间。
  • 排查重点是锁在哪个索引上、阻塞哪个间隙,以及事务何时提交,而不是只看 SQL 表名。

先区分快照读和真正的锁定读

普通 SELECT 默认是非锁定一致性读,不会因为读到了某个范围就自动把插入挡住。SELECT ... FOR UPDATESELECT ... FOR SHARE,以及修改数据的 UPDATEDELETE 才会按照索引扫描获取锁。先在同一个会话查看隔离级别,避免把快照读和锁定读混在一起。

-- 查看当前会话的隔离级别;排查时优先确认会话值
SELECT @@SESSION.transaction_isolation;

-- 只在明确需要锁定范围时使用 FOR UPDATE
START TRANSACTION;
SELECT id, score FROM exam_result
WHERE score BETWEEN 80 AND 90
FOR UPDATE;
-- 业务处理完成后尽快提交,避免锁随事务长时间保留
COMMIT;

默认的 REPEATABLE READ 会在范围型锁定读中使用 next-key locking;切换到 READ COMMITTED 后,普通搜索和索引扫描通常不再使用间隙锁,但外键检查和重复键检查仍可能需要它。

按索引顺序把间隙锁画成区间

帮助读者对照隔离级别、索引条件和插入行为做判断。
图2:隔离级别与索引条件关系说明图,不是运行截图或锁监控结果。

间隙锁锁的不是一行数据,而是索引顺序中两条记录之间“不允许插入”的位置。临键锁则是“索引记录锁 + 该记录之前的间隙锁”。假设索引值为 10、11、13、20,可能观察到的临键区间是 (-∞,10](10,11](11,13](13,20](20,+∞)。最后一个区间对应最大值之后的 supremum 伪记录。

MySQL InnoDB 索引值10 11 13 20与间隙锁和临键锁区间关系说明图
图1:索引区间结构说明图,展示临键锁如何覆盖记录前的间隙。

因此,WHERE score BETWEEN 80 AND 90 FOR UPDATE 是否阻塞新插入,不能只看结果集中返回了几行,还要看优化器沿哪个索引扫描、扫描从哪条记录开始、在哪条记录结束。没有合适索引时,扫描范围和锁持有量都可能扩大。

唯一等值与范围条件要分开判断

完整使用唯一索引做等值查找,例如主键 id = 100,InnoDB 通常只需要锁住命中的索引记录,不必锁住它之前的间隙。若唯一索引只使用了部分列,或者条件落在非唯一索引、范围谓词上,就不能套用这个结论。

条件常见锁范围插入判断
完整唯一索引等值记录锁为主邻近间隙通常可插入
非唯一索引等值扫描到的记录及前方间隙可能阻塞相同范围的新值
范围条件索引扫描区间的间隙/临键锁区间内插入可能等待
READ COMMITTED 普通扫描通常只保留记录锁更容易插入,但要接受幻读边界

这个表只能作为定位起点。真正结论要用 EXPLAIN 确认索引和访问类型,再用锁等待信息确认实际持锁对象。

用两个会话复现“插入等待”

准备一个有索引的表。会话 A 先锁定分数区间但不提交,会话 B 尝试插入区间内的新分数;如果 B 等待,说明它撞上的是 A 扫描范围设置的抑制性间隙锁,而不一定是某一条已存在的行。

-- 会话 A:锁住索引范围,故意保持事务未提交
START TRANSACTION;
SELECT id FROM exam_result
WHERE score BETWEEN 80 AND 90
FOR UPDATE;

-- 会话 B:新值落入同一索引区间时可能等待
START TRANSACTION;
INSERT INTO exam_result(id, score) VALUES (901, 85);
-- 这里的等待对象应结合 data_locks 判断,不要只看客户端卡顿
ROLLBACK;

复现时同时记录 SHOW CREATE TABLEEXPLAIN 和两会话的提交时间。若谓词没有命中预期索引,先解释“实际扫描了什么”,再讨论锁范围;否则很容易把索引设计问题误判为隔离级别问题。

用锁等待信息收敛处理方案

MySQL 8.4 可从 Performance Schema 的 data_locksdata_lock_waits 观察等待关系。重点看阻塞事务、请求事务、表名、索引名和锁类型;事务是否长时间不提交,往往比“是不是间隙锁”更直接地决定事故持续时间。

-- 查看等待边:谁在等、谁在阻塞
SELECT w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx,
       w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx
FROM performance_schema.data_lock_waits AS w;

-- 收敛方案:缩小索引范围、缩短事务、提前提交
-- 只有业务允许出现幻读时,才评估 READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

生产环境的判断清单是:索引是否覆盖过滤条件;是否能把锁定读改成唯一等值;事务是否夹带了网络调用;是否必须保持范围内不可插入;切换隔离级别后是否接受幻读和日志格式约束。先缩短持锁时间,再考虑放宽隔离级别。

常见问题

间隙锁会锁住一整张表吗?

它锁的是索引间隙,范围过大或缺少合适索引时效果可能接近“谁都插不进去”,但概念上仍不是表锁。

两个事务能同时持有同一间隙锁吗?

间隙锁是抑制插入的锁,不同事务的间隙锁可以共存;真正插入时才会与该间隙上的抑制关系发生等待。

把隔离级别改成 READ COMMITTED 就一定解决吗?

不一定。它通常减少普通扫描的间隙锁,但外键和重复键检查仍可能加锁,记录锁冲突和长事务也不会自动消失。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>