当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL InnoDB 隔离级别并发更新为什么出现间隙锁等待怎样完成一个可复用的最小示例

MySQL InnoDB 隔离级别并发更新为什么出现间隙锁等待怎样完成一个可复用的最小示例

来源:17golang原创 2026-11-10 00:11:46 0浏览 收藏

在订单、库存或任务表上做并发更新时,最让人困惑的现象往往是:两条 SQL 改的不是同一行,第二个事务却一直等待。很多时候,等待的对象不是已经存在的记录,而是索引记录之间的“空档”。这就是 InnoDB 间隙锁参与后的典型表现。

本文只解决一个边界问题:怎样用一个可重复的最小示例看清隔离级别、范围条件和索引之间的关系。结论先说清楚:默认 REPEATABLE READ 下,范围更新或锁定读可能拿到 next-key lock,它由记录锁和前面的间隙锁组成;如果业务允许,可以评估 READ COMMITTED,但不能把降低隔离级别当成无条件修复。

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

先把“更新不同记录”变成可观察的等待

准备一个隔离测试库和两个客户端会话。示例使用 slot_no 作为非唯一索引,故意让两个事务访问同一个索引范围中的不同值。代码只描述复现材料,不代表已经在本文环境执行。

-- 建议在独立测试库执行,避免触碰业务表
CREATE TABLE inventory_gap_demo (
  id BIGINT NOT NULL PRIMARY KEY,
  slot_no INT NOT NULL,
  state VARCHAR(16) NOT NULL,
  quantity INT NOT NULL,
  KEY idx_slot_no (slot_no)
) ENGINE=InnoDB;

-- 插入有空档的索引值,方便观察范围前后的锁覆盖
INSERT INTO inventory_gap_demo (id, slot_no, state, quantity) VALUES
  (1, 10, 'ready', 8),
  (2, 20, 'ready', 6),
  (3, 30, 'ready', 4);

-- 固定当前会话的隔离级别,避免复现时沿用未知连接状态
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

在会话 A 中先锁住一个范围,再保持事务不提交:

-- 会话 A:范围更新会沿 idx_slot_no 查找并加锁
START TRANSACTION;
UPDATE inventory_gap_demo
SET state = 'reserved'
WHERE slot_no >= 20 AND slot_no 

在会话 B 中更新范围内的另一个候选值:

-- 会话 B:不同 SQL 不代表一定没有索引范围冲突
START TRANSACTION;
UPDATE inventory_gap_demo
SET quantity = quantity - 1
WHERE slot_no = 25;

-- 如果正在等待,可在另一个管理会话查看等待链
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME,
       LOCK_TYPE, LOCK_MODE, LOCK_STATUS
FROM performance_schema.data_locks
WHERE OBJECT_NAME = 'inventory_gap_demo';

这个构造的关键不是某个固定超时数字,而是“会话 A 保持范围锁,会话 B 试图在同一索引次序中写入或更新”。如果数据分布、优化器选取的索引或版本配置不同,等待细节也会不同,不能把一条示例输出当成所有生产场景的固定结果。

MySQL InnoDB 索引记录、范围条件与间隙锁覆盖的静态关系说明图
图1:索引记录、范围谓词和锁对象的静态说明图,不是截图或运行证据;重点看记录之间的空档为何也会成为并发边界。

为什么范围更新会锁住记录之间的空档

InnoDB 的记录锁锁住的是索引记录,而间隙锁锁住的是索引记录之间的范围。官方文档把 next-key lock 定义为“记录锁 + 记录前面的间隙锁”。在默认的 REPEATABLE READ 下,搜索或扫描索引时会使用这种组合来防止幻读。

因此,会话 A 的范围不是简单的“只改一条已有记录”。它包含一个索引搜索边界:符合条件的记录需要被保护,边界内可能出现的新索引记录也需要被阻止。会话 B 即使准备写入当前不存在的 slot_no = 25,仍可能触碰会话 A 持有的间隙。

有三个概念不要混在一起:

  • 记录锁:保护已经存在的索引记录。
  • 间隙锁:抑制其他事务向索引空档插入,间隙锁彼此可以共存。
  • next-key lock:把记录锁和前方间隙锁组合起来,常见于可重复读下的范围扫描。

这也是“更新的是不同业务对象,为什么还会等待”的原因:数据库竞争的是索引范围,不只是业务主键的相等值。

唯一等值条件为什么通常不会扩大成间隙锁

把范围条件和唯一索引上的唯一等值条件放在一起比较,通常更容易定位根因。官方文档明确指出,使用唯一索引搜索唯一行时不需要间隙锁;但联合唯一索引只使用部分列时,仍可能发生间隙锁。

-- 用唯一主键等值访问单行,锁的边界通常更窄
START TRANSACTION;
UPDATE inventory_gap_demo
SET quantity = quantity - 1
WHERE id = 2;

-- 用范围谓词访问索引区间,不能按“只改不同 id”推断没有冲突
UPDATE inventory_gap_demo
SET quantity = quantity - 1
WHERE slot_no >= 20 AND slot_no 

这里的对比不是说主键等值更新永远不会等待:如果另一个事务已经锁住同一条记录,记录锁仍然会冲突。准确说法是,唯一索引上的唯一等值访问通常不会额外锁住搜索范围的间隙,等待对象更容易缩小到实际记录。

生产排查时要同时看谓词和执行路径。列上有索引,并不等于一定使用了你想象的索引;非唯一索引、联合索引的前缀条件和范围扫描都可能改变锁覆盖。

把 REPEATABLE READ 与 READ COMMITTED 放在同一张决策表

READ COMMITTED 可以显式关闭搜索和索引扫描的间隙锁,官方文档也说明外键检查和重复键检查仍可能使用间隙锁。它减少的是一类锁覆盖,不是把所有并发写冲突清零。

观察点REPEATABLE READREAD COMMITTED判断重点
范围搜索可能使用 next-key lock搜索和扫描通常不使用间隙锁是否依赖阻止幻读
同一已有记录仍可能发生记录锁等待仍可能发生记录锁等待是否改到了同一行
外键/重复键可能涉及间隙锁仍可能涉及间隙锁是否属于约束检查
业务语义快照一致性更强读取到已提交的新版本机会更多是否能接受读视图变化

不要为了消除一次等待就全局改隔离级别。先确认业务是否需要同一事务内的稳定读视图,再在单个连接或明确的工作负载上评估。隔离级别是并发语义选择,不是锁等待参数。

用锁对象和事务边界判断修复方向

实际处理时,可以按“范围是否必要、索引是否匹配、事务是否过长、隔离语义是否可降级”四个问题做决策。

-- 先在等待发生时查看当前锁对象,不要凭 SQL 文本猜测
SELECT ENGINE_TRANSACTION_ID, OBJECT_SCHEMA, OBJECT_NAME,
       INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS,
       LOCK_DATA
FROM performance_schema.data_locks
WHERE OBJECT_SCHEMA = DATABASE()
  AND OBJECT_NAME = 'inventory_gap_demo';

-- 只为当前排查会话设置超时,避免把超时当成根因修复
SET SESSION innodb_lock_wait_timeout = 5;

-- 事务只包住必要的读写,完成后尽快释放锁
COMMIT;
  1. 必须阻止范围内新行吗?如果库存占位、号段分配等逻辑需要防止并发插入,间隙锁可能是语义的一部分,不能简单删除。
  2. 能否改成唯一键等值访问?如果业务对象有稳定唯一标识,优先让更新走唯一条件,避免用宽范围谓词代替精确定位。
  3. 索引是否覆盖真实过滤条件?检查联合索引顺序和访问条件,避免因为缺索引而扫描更宽范围;索引调整仍需单独评估写入成本。
  4. 事务是否夹带了慢操作?把网络调用、人工确认和无关查询移出事务,缩短持锁时间,通常比盲目调大等待超时更直接。
  5. 能否在局部使用 READ COMMITTED?只有在读视图和幻读语义都可接受时,才在限定连接或工作负载中改用,并补上回归检查。

如果日志里看到的是 locks gap before rec、范围索引或 next-key 相关信息,先确认锁对象和持有事务,再决定改 SQL、改索引、缩短事务还是调整隔离级别。不要只看“第二条 UPDATE 等了几秒”这个表象。

MySQL 间隙锁排查中隔离级别、索引、事务边界与修复选择的静态关系图
图2:修复决策的静态边界图,不是截图或运行证据;它把业务语义、访问条件、持锁时间和隔离级别放到同一判断面上。

哪些做法看似有效,实际上没有解决根因

  • 只把 innodb_lock_wait_timeout 调大:它延长等待,不会改变谁持有范围锁。
  • 只给非唯一范围列加一个索引:索引可能让扫描更精准,也可能改变访问路径,但是否减少锁覆盖仍要看谓词和唯一性。
  • 看到两个值不同就认定互不冲突:如果它们位于同一个被保护的索引区间,仍会共享竞争边界。
  • 直接全局切换 READ COMMITTED:可能改变快照读取和业务一致性,必须先确认应用语义。
  • 让事务长期保持打开:即使 SQL 本身很快,锁也会被后续业务动作拖长。

结论:先认清锁住的是“范围”,再选择缩小边界

InnoDB 并发更新出现间隙锁等待,通常不是数据库随机锁住了无关数据,而是范围谓词、索引顺序和隔离级别共同决定了保护范围。默认 REPEATABLE READ 下,next-key lock 会同时关注索引记录和前方间隙;唯一索引上的唯一等值访问通常可以把边界缩小到记录本身;READ COMMITTED 可减少搜索和扫描的间隙锁,但仍保留记录锁及约束检查相关的竞争。

可复用的排查顺序是:先在隔离库还原两个会话,再查看实际锁对象,确认索引和谓词,最后结合业务一致性选择唯一等值访问、合适索引、更短事务或局部隔离级别调整。

相关问题

  • 为什么唯一索引的部分列条件仍可能产生间隙锁?
  • InnoDB 记录锁等待和死锁应该分别看哪些字段?
  • 把范围更新改成先查主键再按主键更新有什么代价?
版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL GET DIAGNOSTICS CONDITION 怎样在异常处理里拿到真正的错误信息MySQL GET DIAGNOSTICS CONDITION 怎样在异常处理里拿到真正的错误信息
上一篇
MySQL GET DIAGNOSTICS CONDITION 怎样在异常处理里拿到真正的错误信息
MySQL InnoDB 隔离级别并发更新为什么出现间隙锁等待为什么结果为空或不符合预期
下一篇
MySQL InnoDB 隔离级别并发更新为什么出现间隙锁等待为什么结果为空或不符合预期
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    965次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    965次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1077次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1055次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    884次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码