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

MySQL 事务隔离级别怎样缩小锁等待范围

来源:17golang原创

时间:2026-10-19 21:15:38 400浏览 收藏

MySQL 事务锁等待不一定要靠降低隔离级别解决。对于 InnoDB 范围更新,如果主要冲突来自范围中的间隙锁,而且业务允许同一事务的多次普通读取看到不同已提交数据,可以考虑 READ COMMITTED;如果两个事务更新的是同一条记录,改成 READ COMMITTED 仍然可能等待。

以下按 MySQL 8.4 官方规则解释,仅讨论隔离级别与锁等待,不给出生产实测结果。官方资料:https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html。

从范围写入识别等待对象

设想一个订单后台:事务 A 调整某租户一段业务编号内的状态,事务 B 同时插入该区间的新订单。此时要先区分 B 是在等插入位置,还是在更新 A 已经修改的记录。两类等待不能用同一个“降级就好”的结论处理。

把请求中的租户编号、范围上下界和操作类型对应到 SQL,再看它实际使用的索引。业务 WHERE 看起来很窄,并不等于执行时扫描的索引范围就窄;缺少合适索引时,隔离级别不是唯一变量。

确认业务能否接受逐次读取变化

采用 READ COMMITTED 之前,要回答“事务内第二次普通查询看到新提交的数据,业务是否还能正确决策”。如果程序依赖两次查询的范围集合不变,放宽规则就可能让后续处理对象发生变化。不能只比较等待时间,却忽略读取口径。

REPEATABLE READ 的普通一致性读与 UPDATE、锁定读也不是同一条读取路径。不要因为先前 SELECT 没看到某条记录,就推断后续写入没有并发冲突。隔离级别选择应落实到具体事务,而非给所有接口贴统一标签。

等待或需求应关注的方向
范围附近插入被阻塞检查间隙锁及业务能否接受 RC
同一已有记录被多个事务更新检查记录冲突及持锁事务时长
希望多次读取集合保持一致先保留一致性要求,不单为减少等待改设置
扫描远多于目标记录先检查索引访问范围

把索引范围与记录冲突分开

在 REPEATABLE READ 下,非唯一范围搜索可能涉及 next-key 锁及间隙;READ COMMITTED 的相关范围操作通常仅锁索引记录,外键与重复键检查除外。对于 UPDATE、DELETE,不匹配行的记录锁也有较早释放的规则。后者不表示匹配且被修改的记录没有锁。

按唯一索引精确找到一条已有记录,本来就与非唯一范围搜索不同。分析时同时记录访问索引、搜索方式和事务是否结束。锁与扫描范围的官方说明:https://dev.mysql.com/doc/refman/8.4/en/innodb-locks-set.html。

范围条件、索引访问、记录锁和间隙约束的静态关系说明图
原创说明图:分组表示查询条件和锁对象,不是某次查询的真实锁清单。

仅为下一笔事务设置隔离级别

对独立测试连接,可以先设置下一笔事务的隔离级别,再开始事务。不要在已经执行 START TRANSACTION 的事务中尝试改变它。

-- 仅影响当前连接的下一笔事务,不修改全局默认值。
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;

-- 假设表有主键 (tenant_id, order_no),字段非空。
UPDATE order_lock_demo
SET state_code = 2
WHERE tenant_id = 7
  AND order_no >= 100
  AND order_no 

无 SESSION 或 GLOBAL 的 SET TRANSACTION 只作用于下一笔事务;SESSION 会影响连接后续事务,连接复用时更容易把设置带给别的请求。务必在同一物理连接上设置并启动事务,不能让连接池分别分配两条命令。语法与作用域见:https://dev.mysql.com/doc/refman/8.4/en/set-transaction.html。

用两会话对照解释剩余等待

为了对照范围插入和同记录更新,下面提供测试表及两个会话的命令。普通临时表不适合这个跨会话示例,因此使用专用测试库内的 InnoDB 表;确认同名对象不存在后再创建,不在生产库执行。

-- 仅在专用测试库创建一次;两会话必须连接到同一个测试库。
CREATE TABLE order_lock_demo (
    tenant_id INT NOT NULL,
    order_no INT NOT NULL,
    state_code INT NOT NULL,
    PRIMARY KEY (tenant_id, order_no)
) ENGINE = InnoDB;

-- 数据中预留编号 150,避开重复键与外键检查的影响。
INSERT INTO order_lock_demo VALUES
(7, 110, 1), (7, 190, 1), (7, 250, 1);

会话 A 先执行下面的范围更新,暂时保留事务。对比时分别在事务开始前选择 REPEATABLE READ 或 READ COMMITTED,其余 SQL 和数据保持一致。

-- 会话 A:该轮使用 RR;下一轮可改为 READ COMMITTED。
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
UPDATE order_lock_demo
SET state_code = 2
WHERE tenant_id = 7 AND order_no >= 100 AND order_no 

会话 B 每轮只选择下面一个实验。先观察插入,再把两端事务结束、恢复初始状态后观察更新已有记录,不要把几个实验堆在同一笔未结束事务里。

-- 会话 B 实验一:尝试插入 A 搜索范围内的新位置。
START TRANSACTION;
INSERT INTO order_lock_demo VALUES (7, 150, 1);
-- 命令返回后回滚;若它正在等待,需要先结束会话 A 的事务。
ROLLBACK;

-- 会话 B 实验二:在全新一轮中尝试修改 A 已锁定的记录。
START TRANSACTION;
UPDATE order_lock_demo
SET state_code = 3
WHERE tenant_id = 7 AND order_no = 110;
-- 即使 A 使用 RC,同记录写冲突仍可能等待。
ROLLBACK;

在示例前提和对应主键范围访问下,预期 RR 与 RC 在插入空位的行为上可能体现差异,而修改已有记录仍受到记录锁约束。这是供读者自行观察的预期,不是本文已经测出的结果。若现象不同,应保留实际访问路径和锁对象,不能照图猜测。

事务设置作用域、范围插入、同记录更新与事务结束的责任关系说明图
原创说明图:下一事务设置不等于会话默认;插入位置与已有记录是两种判断对象。

异常结束后及时释放事务资源

失败路径同样需要明确事务结尾。应用遇到等待超时、取消或其他 SQL 错误时,不要假设所有错误都会替你结束整笔事务。由事务管理层决定回滚,并确认连接可以安全复用;不能把长时间打开的事务交回池中继续持有资源。

演示清理时先让 A 回滚,再等待 B 的命令返回并回滚。两个会话都结束事务后,方可在专用测试库删除示例表。

-- 仅在确认两会话事务都已结束后清理专用测试对象。
DROP TABLE order_lock_demo;

RC 能消除死锁吗?

不能。它可能减少某些锁冲突,但多个事务仍可能争用相同记录;应用需要明确失败处理,不能把隔离级别当作免死锁开关。

能否先全局改成 RC 看效果?

不应把全局变更当作一个接口的排障试探。先限定事务、确认读取语义,并与现有复制及部署要求协调,本文不提供全局修改步骤。

缩短锁等待最先改什么?

先识别等待对象:记录冲突看持锁事务和操作范围;间隙相关冲突才讨论隔离级别。无论选择哪一级,都应让事务只包住必要的数据库操作,并及时结束。

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