MySQL 事务隔离级别与锁定读的组合方案
来源:17golang原创
时间:2026-10-31 23:22:41 271浏览 收藏
MySQL 事务隔离级别与锁定读的组合方案
InnoDB 中,隔离级别决定读视图和部分锁策略,锁定读则用于保护随后要依赖或修改的数据。按已存在主键读取库存再扣减,可以在 READ COMMITTED 或 REPEATABLE READ 下使用同一事务中的 FOR UPDATE;只把隔离级别调高,并不能替代清晰的事务边界、索引条件和错误处理。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/。本文以 MySQL 8.4 的 InnoDB 语义为范围,示例使用独立演示库。
先确定要保护哪项库存不变量
假设一个SKU只有一条库存记录,每次预留数量必须为正,成功后可用库存不能为负。这里保护的是单行数量变化,不包括订单创建、跨仓调拨和支付。先把目标缩小,才能判断需要普通读取、共享锁还是排他锁。
如果只是查询页面显示数量,普通SELECT通常更符合读取目的;如果必须先依据数量作决定,再修改同一行,就应考虑FOR UPDATE。若唯一任务是“库存足够就减掉数量”,带数量条件的单条UPDATE也能表达该不变量,不必为了形式完整额外读一次。本文保留锁定读,是为解释读后决策场景。
准备 InnoDB 与主键访问条件
下面的表用主键精确定位SKU。先在独立演示库准备表和虚构数据,再创建过程。DDL与库存预留不放进同一个业务事务,避免混淆事务控制。
-- 在独立演示库中准备表;DDL不放进业务事务。
CREATE TABLE stock_demo (
sku_id BIGINT PRIMARY KEY,
available INT NOT NULL,
CONSTRAINT ck_stock_demo_nonnegative CHECK (available >= 0)
) ENGINE = InnoDB;
-- 使用虚构SKU,避免操作真实库存。
INSERT INTO stock_demo (sku_id, available) VALUES (701, 20);
示例讨论的是已经存在的主键记录。若查询范围、联合索引条件或不存在记录发生变化,锁的覆盖也需要重新判断;不能根据WHERE里写了一个等号就认定永远只锁一条记录。

按读写目标组合隔离级别
| 组合 | 适合讨论的目标 | 需要注意 |
|---|---|---|
| READ COMMITTED与普通SELECT | 按语句读取已提交数据 | 同一事务多次读取可能不同 |
| REPEATABLE READ与普通SELECT | 需要一致读视图的读取 | 读视图不是未来修改的排他权 |
| READ COMMITTED与FOR UPDATE | 锁住当前要修改的记录 | 不能依赖它阻止一般范围内的新行插入 |
| REPEATABLE READ与FOR UPDATE | 结合当前锁定读与相应索引锁策略 | 范围扫描和唯一精确命中的锁覆盖不同 |
| FOR SHARE | 保护读取所依赖的记录 | 读后还要升级写锁时可能产生竞争 |
官方 隔离级别说明区分了两种一致读视图,也说明RC通常不使用间隙锁,但外键和重复键检查有例外。RR的范围锁定不能套用到RC。锁定读使用的状态与RR早先建立的一致读视图也可能不同,不应混用两次查询的值作同一判断。
在一个事务里完成锁定与扣减
下面给出mysql命令行客户端可使用的存储过程示例。它拥有事务,调用时必须确保连接没有活动事务;不要从外层订单事务中调用,以免START TRANSACTION影响已有事务边界。过程只演示单SKU库存扣减,不是完整订单系统。
-- 以下DELIMITER是mysql客户端指令,不是应用驱动SQL。
DELIMITER //
CREATE PROCEDURE reserve_stock_demo(IN p_sku BIGINT, IN p_qty INT)
SQL SECURITY INVOKER
BEGIN
DECLARE v_available INT DEFAULT NULL;
-- SQL失败时结束整个事务,并把错误交还调用方。
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_available = NULL;
-- 本过程拥有事务,只能在无活动事务的连接上调用。
IF p_sku IS NULL OR p_qty IS NULL OR p_qty = p_qty;
IF ROW_COUNT() 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'reservation rejected';
END IF;
COMMIT;
END//
DELIMITER ;
-- 在无活动事务的同一连接上,为下一事务指定隔离级别。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
CALL reserve_stock_demo(701, 2);
过程先检查输入,再开启事务。FOR UPDATE取得库存值,数量不足或SKU不存在时抛错;异常处理器回滚并重新抛出,成功路径提交。调用方只有收到成功响应才能确认本次预留结果。代码未在本文制作中执行,不包含实测锁等待或数据库截图。
要改成RR,可在无活动事务的连接上把SET TRANSACTION语句中的READ COMMITTED替换为REPEATABLE READ;其余单行逻辑不必因此改为普通SELECT。此处不使用SET SESSION,避免示例把隔离级别长期留在连接池会话上。应用接入时仍应让设置与事务在同一物理连接执行,并确认驱动的连接复用行为。

锁要等事务结束才释放,不要在持锁期间等待支付接口、人工确认或慢速外部请求。FOR UPDATE也不意味着普通一致读全部被阻塞,其他事务仍可能读取其读视图中的版本。具体规则见 锁定读文档。
把权限和错误处理放进事务边界
示例采用SQL SECURITY INVOKER,运行时使用调用者权限,不借过程定义者身份扩大访问范围。创建过程与调用过程使用的账号职责应分开。MySQL 8.4中FOR UPDATE需要SELECT以及UPDATE、DELETE、LOCK TABLES中的至少一种权限;本例本来就要UPDATE库存,不需要为了锁定读另外授予删除能力。
死锁与等待超时必须作为业务失败处理,不要在事务状态不明确时只重发最后一条UPDATE。该过程的SQL异常路径主动回滚;若应用选择重试,应建立新事务、限制次数并采用退避。连接中断或提交响应丢失时,调用方还可能无法判断提交是否生效,因此真实预留接口需要独立业务请求号与幂等记录。本例没有这张记录表,不能直接无条件重试扣减。
日志宜记录业务请求号、SKU标识、请求数量、错误类别和耗时,避免输出连接口令或完整客户数据。锁等待与失败计数有助于定位争用,但不能仅凭接口超时判断“事务一定没提交”。
接入前的检查与常见问题
在自己的演示环境中,可以用两个独立连接请求同一SKU,观察竞争是否遵守预期,并覆盖库存不足、缺失SKU、非法数量和错误回滚路径。接入时还应确认主键访问条件、事务是否过长、存储过程权限和客户端错误处理。这些是读者的接入建议,本文发布流程未运行数据库校验。
能否用FOR SHARE读取后再修改?可以出现锁升级竞争。明确准备修改同一行的业务,优先把排他锁意图写清;不能把共享锁当作没有代价的FOR UPDATE替代品。
SKIP LOCKED可以防止库存接口等待吗?它会排除已锁记录,可能把正在竞争的SKU表现为空结果。适用于允许跳过记录的队列等语义,不应直接替换普通库存查询。
选择RC就不再有死锁吗?不是。隔离级别调整不能取消所有锁竞争;多行业务仍需要一致的锁定顺序、短事务和有界重试。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
118 收藏
-
306 收藏
-
368 收藏
-
335 收藏
-
175 收藏
-
374 收藏
-
154 收藏
-
432 收藏
-
232 收藏
-
287 收藏
-
233 收藏
-
417 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习