read committed 是多数 web 应用的合理默认,因其避免脏读、开销低、并发好,且能满足订单、支付等业务需求;它不锁间隙,减少死锁,主从一致性更优,而 repeatable read 和 serializable 分别因间隙锁和全局读锁导致性能显著下降。

READ COMMITTED 为什么是大多数 Web 应用的合理默认
多数业务系统(比如订单、支付、用户资料更新)不需要可重复读,但必须避免脏读;READ COMMITTED 在 MySQL 默认的 InnoDB 引擎下开销低、并发好,且能拦住明显错误数据。
常见错误现象:SELECT 看到未提交事务写入的中间状态(脏读),或在同一个事务里两次 SELECT 返回不同结果(不可重复读)但业务其实不依赖第二次结果一致性——这时硬上 REPEATABLE READ 反而增加锁竞争和间隙锁风险。
- 电商下单时查库存 → 改库存 → 扣减,只要不读到别人还没
COMMIT的扣减数就足够,不需要两次查库存一定相等 -
READ COMMITTED下,普通SELECT是快照读(不加锁),UPDATE/DELETE只锁匹配到的行,不锁间隙 - MySQL 8.0+ 开启
binlog_format = ROW时,READ COMMITTED对主从一致性更友好
REPEATABLE READ 要小心间隙锁和幻读放大效应
InnoDB 的 REPEATABLE READ 实现靠的是事务启动时拍快照 + 间隙锁(Gap Lock),不是单纯 MVCC;它确实能保证同一事务内多次 SELECT 结果一致,但代价是更容易锁表、死锁、阻塞并发插入。
使用场景:银行对账、财务报表生成这类强一致性读需求,且读操作集中在固定主键范围或索引覆盖范围内;如果业务有大量 INSERT ... SELECT 或范围 SELECT ... FOR UPDATE,REPEATABLE READ 会默默锁住整个区间。
- 执行
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE,InnoDB 可能锁住所有status值为 'pending' 的间隙,哪怕新插入的记录还没存在 - 没有合适索引时,
REPEATABLE READ可能升级为全表扫描 + 全表间隙锁,导致 INSERT 被卡住 - 切换到
READ COMMITTED后,同样语句只锁实际命中的行,不锁间隙,但需确认业务是否接受“两次读之间有新记录插入”
Serializable 不是“最安全”,而是“最慢且最难调”
SERIALIZABLE 会让所有普通 SELECT 隐式加上 LOCK IN SHARE MODE,相当于给每条查询加读锁,写操作基本排队执行;它解决幻读的方式是彻底禁止并发写,而不是优化锁粒度。
性能影响极明显:QPS 掉 3–5 倍很常见,监控里会看到大量 Waiting for table metadata lock 或 Locked 状态线程;除非你在做跨库/跨表强一致校验(比如迁移核对脚本),否则几乎没理由在线上业务库启用。
- 开发环境用
SERIALIZABLE复现死锁?不如直接在REPEATABLE READ下用SHOW ENGINE INNODB STATUS查事务锁信息 - 某些 ORM(如 Django)在事务里执行
SELECT后跟INSERT,若隔离级别是SERIALIZABLE,可能触发意料外的锁等待甚至超时 - MySQL 官方文档明确说:
SERIALIZABLE是为“理论验证”准备的,不是生产推荐配置
怎么验证当前连接用的是哪个隔离级别
别只信配置文件里的 transaction_isolation,MySQL 允许会话级覆盖;线上出问题时,得看实际执行 SQL 的那个连接到底在用什么级别。
实操建议:在应用代码关键事务入口处(比如 Go 的 db.BeginTx() 后、Python 的 connection.begin() 后)执行一次检查语句,比事后排查快得多。
- 查当前会话:
SELECT @@transaction_isolation;(返回类似'REPEATABLE-READ') - 查全局默认:
SELECT @@global.transaction_isolation; - 临时修改会话级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,但注意有些 ORM(如 SQLAlchemy)会在连接池取连接时重置会话变量 - Spring Boot 应用里,
@Transactional(isolation = Isolation.READ_COMMITTED)只影响该注解作用域,不影响其他事务
真正麻烦的从来不是选哪个级别,而是同一套代码在不同环境(本地 / 测试 / 生产)用了不同配置,又没日志输出实际生效值。上线前跑个 SELECT @@transaction_isolation 检查连接,比背理论管用。











