rc模式下update执行后仅保留实际修改行的行锁,未匹配行锁立即释放;rr则对扫描的所有行加next-key lock并持至事务结束,导致锁范围大、等待多。

RC模式下UPDATE语句执行完就释放非匹配行锁
在READ COMMITTED级别,UPDATE语句执行结束后,InnoDB只保留对**实际被修改行**的行锁(record lock),所有扫描过但未命中WHERE条件的行,其锁会立刻释放。这和REPEATABLE READ完全不同——RR下哪怕只更新1行,只要扫描了1000行,这1000行都会被加上next-key lock并一直持有到事务结束。
常见错误现象:Lock wait timeout exceeded在RR下高频出现,但在RC中大幅减少,尤其当WHERE条件没走索引(触发全表扫描)时,RC仍只锁命中的几行,而RR直接锁住整个主键范围。
- 验证方式:执行
UPDATE t SET x = 1 WHERE name = 'alice'后立刻查SELECT * FROM information_schema.INNODB_TRX,RC下trx_rows_locked值接近实际更新数;RR下可能高达数万 - 性能影响:RC避免了长事务期间锁等待链扩散,尤其适合带RPC调用、消息队列回调等耗时逻辑的业务事务
- 注意前提:必须确保
WHERE条件能命中索引,否则RC虽不加间隙锁,但全表扫描+逐行加记录锁,依然慢且资源占用高
RC不使用间隙锁(Gap Lock),锁范围天然更小
间隙锁是RR为防止幻读引入的机制,它锁住的是“值之间的空隙”,比如WHERE id > 10在RR下会锁住(10, +∞)这个区间,导致新插入id = 11被阻塞;而RC完全不加这类锁,只锁已存在的、满足条件的记录本身。
典型场景:电商库存扣减UPDATE stock SET count = count - 1 WHERE sku_id = 'xxx',若sku_id无索引:
- RR下:全表扫描 → 每行加next-key lock → 等效表级锁,其他SKU插入/更新全部排队
- RC下:全表扫描 → 每行加record lock → 仅锁住匹配的那几行,其余行可自由操作
即使有索引,RC也避免了相邻值插入被卡住的问题——比如sku_id = 'xxy'在RR下可能因间隙锁被堵,RC里完全不受影响。
RC每次SELECT都用新快照,不拖累undo log生命周期
RC下每个SELECT语句开始时,都基于“该时刻最新已提交版本”构建一致性视图;而RR复用事务启动时的快照。这意味着RC不需要长期维护旧版本数据,undo log可以更快被purge线程清理。
这对批量更新的影响是间接但关键的:
- 长事务(如分批更新循环)在RR下会持续hold住大量历史版本,导致
history list膨胀、磁盘空间增长、purge线程压力大 - RC下,即使事务运行10分钟,中间的
SELECT也不会拖住老版本,undo空间回收及时,IO压力更平稳 - 若应用层有隐式长事务(如ORM自动开启未显式commit),RC的资源堆积风险远低于RR
批量更新必须配合显式分批+COMMIT,否则RC优势失效
RC的锁释放快,前提是事务不能太长。如果写一个超大UPDATE语句(比如WHERE created_at > '2026-01-01'),哪怕在RC下,它也会一次性锁住所有匹配行,直到整个语句执行完才释放——这仍是单次长锁。
真正发挥RC优势的做法是把大更新拆成小事务:
- 用
WHERE id BETWEEN ? AND ?按主键分段,每批UPDATE后立刻COMMIT - 或用
LIMIT循环更新,靠ROW_COUNT()判断是否继续,每次更新完都COMMIT - 避免在同个事务里混杂
SELECT、远程调用、JSON解析等耗时操作,否则锁虽然释放得早,但事务整体还是长的
最容易被忽略的一点:RC的锁释放快,不等于“可以随便写大SQL”。它只是让单条语句的锁生命周期变短,而不是免除分批义务。没分批的RC批量更新,照样会阻塞其他业务。











