mysql中隔离级别的选择与并发控制优化

来源:这里教程网 时间:2026-02-28 20:42:04 作者:

为什么 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。锁只是症状,事务设计才是根因。

相关推荐