READ COMMITTED 是大多数业务的合理起点,因其不使用间隙锁、锁粒度更小、并发更高,适用于订单更新、库存扣减等对延迟敏感且不强依赖事务内多次读一致性的场景。

为什么 READ COMMITTED 是大多数业务的合理起点
MySQL 默认隔离级别是 REPEATABLE READ,但它在可重复读语义下依赖间隙锁(gap lock)来防止幻读,这会显著放大锁范围,尤其在 WHERE 条件未命中索引或使用范围查询时,容易导致锁冲突和死锁。而 READ COMMITTED 下,每次快照读都基于语句开始时刻的最新已提交版本,不使用间隙锁(仅用记录锁),锁粒度更小、并发更高,且能避免大部分因间隙锁引发的阻塞问题。
适用场景包括:订单状态更新、库存扣减、日志写入等「读-改-写」链路短、不强依赖事务内多次读一致性、但对响应延迟敏感的业务。
- 切换前确认应用没依赖
REPEATABLE READ的“事务内多次读结果一致”特性(比如先查再判断再更新,中间不能被其他事务干扰) - 需配合
innodb_locks_unsafe_for_binlog = OFF(默认值),否则可能在 RC 下也退化出间隙锁行为 - 主从复制建议使用
ROW格式,避免 RC + statement 格式下因执行顺序差异引发数据不一致
什么时候必须用 SERIALIZABLE 或显式加锁
SERIALIZABLE 会将所有普通 SELECT 自动转为 SELECT ... LOCK IN SHARE MODE,开销极大,生产环境几乎不用。真正需要强一致控制的场景,应主动用更细粒度的锁:
- 超卖防控:用
SELECT ... FOR UPDATE配合唯一索引条件(如SELECT stock FROM items WHERE id = 123 FOR UPDATE),确保只锁目标行 - 避免幻读关键逻辑:若业务要求「本次事务中新增的数据不能被后续同条件查询看到」,且无法接受应用层重试,则需
SELECT ... FOR UPDATE覆盖范围(如WHERE category = 'A'),但务必确认该字段有合适索引,否则升级为表锁 - 不要依赖
SERIALIZABLE解决并发问题——它掩盖设计缺陷,且吞吐断崖下跌
READ UNCOMMITTED 的真实风险不止是脏读
很多人以为 READ UNCOMMITTED 只是可能读到未提交数据,实际上它还会导致 MySQL 优化器跳过 MVCC 快照机制,直接读取聚簇索引最新物理行。这意味着:
- 可能读到正在被
DELETE但尚未提交的行(即“幽灵行”) - 可能读到
UPDATE中间状态(如半更新的字段值,尤其涉及大字段或二级索引维护时) - 备份工具(如
mysqldump --single-transaction)在 RU 级别下无法保证一致性快照 - 除极少数审计类只读服务(明确接受数据毛刺),不建议在任何业务表上启用
如何验证当前事务实际持有的锁
光看隔离级别不够,得观察运行时锁行为。关键命令只有两个:
SELECT * FROM performance_schema.data_locks;
它显示每个事务当前持有的锁类型(RECORD / GAP / NEXT-KEY)、锁住的索引、事务 ID;配合下面这条查阻塞关系:
SELECT * FROM performance_schema.data_lock_waits;
常见误判点:
-
data_locks不显示意向锁(IX/IS),但它们是行锁前提,排查死锁时必须意识到其存在 - 间隙锁在
REPEATABLE READ下即使没有显式FOR UPDATE也可能自动加,比如UPDATE t SET x=1 WHERE y > 100会锁住 y>100 的所有间隙 - 唯一索引等值查询(含主键)在 RC 和 RR 下都只加记录锁,不会加间隙锁——这是调优突破口
复杂业务逻辑里,并发瓶颈往往不在 SQL 写法,而在事务边界是否过宽、是否在事务内混入 RPC 或文件 IO。锁只是症状,事务设计才是根因。










