隔离级别管读,乐观锁管写——RC/RR 与写冲突的本质

一、本质区别:一句话

  • RC(Read Committed) :每条 SELECT 生成新 read view,每次读都看到最新已提交数据。
  • RR(Repeatable Read) :事务开始生成一次 read view,整个事务看到同一份快照。

RR 解决的是"读-读一致性"------同事务内多次 SELECT 同一行,值不变。

但乐观锁要解决的是"读-写冲突"------你基于读到的旧值算出新值,UPDATE 时会覆盖别人已经提交的修改。

差别只在 ------读一致性不同。的行为两者一样。

这是两个完全不同的问题 。RR 把第一个问题解决了,但对第二个问题毫无帮助------而且因为快照读让你看不到别人的修改,反而让"基于旧值的覆盖写"更隐蔽。

二、各级别解决什么问题

隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED × × ×
READ_COMMITTED(RC) × ×
REPEATABLE_READ(SQL 标准) ×
REPEATABLE_READ(InnoDB) √(next-key lock 额外解决)
SERIALIZABLE

关键:RC 只解决脏读,不可重复读要 RR 才解决。InnoDB 在 RR 上"超标准"是解决幻读,不是不可重复读。

三、Spring 隔离级别和数据库的关系

Spring 不实现 隔离级别,只是把设置委托给数据库------事务开启时调 Connection.setTransactionIsolation(level),相当于执行 SET SESSION TRANSACTION ISOLATION LEVEL xxx,只对当前会话生效。事务结束后 Spring 自动恢复原级别,不污染连接池。

所以 :MySQL 全局设 RC,Spring 方法上 @Transactional(isolation = RR) 完全可行、无副作用------但生产里不建议混用(间隙锁死锁风险、调试复杂、收益不大)。

四、读一致性 vs 写冲突------两个维度,不可替代

这是几轮讨论的核心。隔离级别和乐观锁解决的是两个完全不同的问题

问题 谁来解决 RC 够吗 RR 够吗
同事务内多次读同一行值一致(读一致性) 隔离级别 ×
基于读到的旧值算新值,UPDATE 时不覆盖别人的修改(写冲突) 乐观锁/原子 SQL × ×

结论 :RR 解决了"读一致性",但没解决"写冲突" ------所以 RC 和 RR 下"读-判-写"模式都需要乐观锁兜底。

RR 下乐观锁的代码、SQL、行为和 RC 下完全一样 ------因为乐观锁的"判断"是数据库在 UPDATE 执行时根据当前行的真实 version 做的,跟事务隔离级别无关

五、RC 和 RR 下乐观锁的代码完全一样

bash 复制代码
UPDATE product_stock 
SET stock = #{newStock}, version = version + 1
WHERE id = #{id} AND version = #{oldVersion}

不管隔离级别是 RC 还是 RR,这条 SQL 都能在写入时校验"我读到的版本是否还有效",rows=0 表示有冲突。乐观锁跟隔离级别无关

六、RC 和 RR 下都会超卖------具体场景

同样并发扣库存,RC 和 RR 结果都一样:超卖。原因相同------基于读到的值算新值,UPDATE 直接覆盖写入,数据库不校验"读到这里"。

RR 反而更隐蔽:快照读让事务感知不到别人的修改,更"自信"地覆盖写入,问题更难排查。

七、实战方案对比

业务模式 推荐方案 适用场景
纯数值加减(库存、余额) SQL 原子操作 SET stock = stock - ? WHERE stock >= ? 不需要先 SELECT
需要拿旧值做校验(订单状态机) 乐观锁 WHERE version = ? 冲突不频繁
需要拿旧值 + 冲突极频繁 悲观锁 SELECT FOR UPDATE 热点行
跨服务/跨库一致性 事务消息 + 本地消息表 / Seata 最终一致

八、大厂主流选型

阿里、美团这类大厂的订单/支付系统:

  • 全局隔离级别 = RC(性能优先,没有间隙锁死锁)。
  • 关键表带 version 字段
  • 纯数值扣减 → SQL 原子操作。
  • 状态机流转 → 乐观锁 WHERE status=? AND version=?
  • 热点行(秒杀) → Redis 预扣 + 异步落库,绕开数据库行锁。
  • 跨服务一致性 → RocketMQ 事务消息 + 本地消息表,最终一致。

九、一句话核心结论

隔离级别管"读",乐观锁管"写",两个维度不可替代

  • 选 RC 还是 RR:影响"读一致性"和"性能/死锁风险",是业务读场景的取舍。
  • 加不加乐观锁:影响"写冲突"是否能兜底,是写场景的必备。

无论 RC 还是 RR,只要代码模式是"先读再基于读到的值算新值并写入",都必须加乐观锁或用原子 SQL。隔离级别选什么,都救不了"基于旧值的覆盖写"。

实战选型一句话

  • RC + 乐观锁:大厂主流,性能优先,乐观锁兜底写冲突。
  • RR + 乐观锁:金融/对账场景,写一致性和读一致性都要,乐观锁还是要加。
  • RR 不加乐观锁 :仅当你的 UPDATE 是"纯数值原子操作"(SET stock = stock - ?)或"无依赖的状态覆盖"(SET status='CANCELLED' WHERE id=?)才安全。任何"先 SELECT 再基于读到的值算新值写入"的模式,RR 下都必须加乐观锁或用原子 SQL

这份总结覆盖前面 4 轮讨论的所有要点。核心就一句话------RC/RR 决定你看到的"读世界",但写入时还是要靠 version 校验,让数据库在写入那一刻确认你看到的世界还成立

相关推荐
熊文豪8 小时前
【金仓数据库征文】不装中间件的 MySQL→金仓在线迁移,mysql_fdw 全流程,和一个差点漏掉的 emoji
数据库·mysql·中间件·电科金仓
C++、Java和Python的菜鸟8 小时前
第5章 后端Web基础 (MySQL基础)
前端·mysql·adb
IT瑞先生10 小时前
MariaDB与Mysql差异及版本对照
数据库·mysql·mariadb
秋风渡.10 小时前
MySQL数据库操作
数据库·mysql
乐橙开放平台11 小时前
养殖 SaaS 笔记:乐橙 IoT 物模型管环境,视频 OpenAPI 管回看
数据库·笔记·物联网·mysql·音视频
鸽芷咕13 小时前
MySQL/PostgreSQL 迁移金仓 KES:LEFT JOIN 丢数据排查与避坑指南
数据库·mysql·postgresql
关关长语1 天前
Wsl解决MySQL容器跨域权限问题
mysql·docker·容器
这个DBA有点耶1 天前
索引碎片与统计信息维护:执行计划突然变差的隐形杀手
mysql·程序员·架构
Mico181 天前
MySQL 8.0.35 GTID 主从复制搭建-基于GITD
android·mysql·adb