MySQL默认且最常用READ COMMITTED,因其在避免脏读前提下兼顾并发性能与开发直觉;普通SELECT为快照读不加锁,UPDATE/DELETE仅锁匹配行、不锁间隙。

READ COMMITTED 为什么是 MySQL 默认且最常用的选择
MySQL InnoDB 默认隔离级别是 READ COMMITTED,不是因为“最安全”,而是它在避免脏读的前提下,兼顾了并发性能与开发直觉。多数业务场景(如订单创建、库存扣减、用户资料更新)不需要可重复读语义,却极度敏感于锁等待和死锁。
-
READ COMMITTED下,普通SELECT是快照读(不加锁),而UPDATE/DELETE只锁匹配到的行(基于聚簇索引),不会锁间隙 - 同一事务内多次执行相同
SELECT,可能看到其他事务已提交的新数据 —— 这是预期行为,不是 bug - 如果你在应用层做了“先查后更”的逻辑(比如查余额 → 判断是否足够 → 扣款),
READ COMMITTED下仍可能因并发导致超扣,必须靠SELECT ... FOR UPDATE显式加锁
REPEATABLE READ 真正锁住的是什么,而不是“数据不变”
很多人误以为 REPEATABLE READ 能保证事务中所有 SELECT 结果一致,其实它只保证“同一查询在事务内返回相同快照”,但这个快照的生成时机是第一次 SELECT 执行时,且它的锁机制比表面更重。
-
UPDATE t SET x=1 WHERE id = 5在REPEATABLE READ下不仅锁住id=5的行,还会锁住前后间隙(Next-Key Lock),防止幻读 - 这意味着一个看似简单的等值更新,可能意外阻塞其他事务对相邻 ID 的插入或更新
- 如果表没有显式主键或唯一索引,InnoDB 会用隐式聚簇索引,此时
REPEATABLE READ的间隙锁范围更难预测,容易引发莫名锁等待
Serializable 会让事务退化成串行执行,但未必解决你想象的问题
SERIALIZABLE 是 SQL 标准定义的最高隔离级别,在 MySQL 中它会让所有普通 SELECT 隐式转为 SELECT ... LOCK IN SHARE MODE,相当于给扫描到的每一行都加共享锁。
- 它确实能杜绝脏读、不可重复读、幻读,但代价是并发能力断崖式下降:两个事务哪怕只读不同行,只要扫描范围有重叠(比如都执行
SELECT * FROM t WHERE created_at > '2024-01-01'),就可能互相阻塞 - 不推荐全局设置为
SERIALIZABLE;若某段逻辑真需要强一致性,应单独用SELECT ... FOR UPDATE+ 明确 WHERE 条件,比升级整个事务级别更精准、开销更低 - 注意:某些 ORM(如 Django 的
select_for_update())在REPEATABLE READ下已能覆盖大部分强一致性需求,不必盲目升到SERIALIZABLE
如何判断当前事务实际生效的隔离级别
MySQL 的隔离级别既可在会话级设置(SET SESSION TRANSACTION ISOLATION LEVEL ...),也可由客户端驱动自动协商(如 JDBC 的 connection.setTransactionIsolation()),但最终以服务端实际生效为准。
- 查看当前会话级别:
SELECT @@transaction_isolation;(MySQL 8.0+ 返回类似READ-COMMITTED) - 查看全局默认:
SELECT @@global.transaction_isolation; - 注意:有些云数据库(如阿里云 RDS)会屏蔽
@@global的修改权限,且可能预设为READ-COMMITTED即使文档写的是 “兼容 MySQL 默认” - 更关键的是,隔离级别只影响本事务内语句的行为;如果业务混用连接池、长事务、异步任务,同一个逻辑可能在不同连接上跑在不同级别下,务必在关键路径日志中打印
@@transaction_isolation值
真正容易被忽略的,是隔离级别和索引策略的耦合关系 —— 没有合适索引时,REPEATABLE READ 的 Next-Key Lock 会升级为全表锁,而 READ COMMITTED 也可能因无法精确定位行而锁更多记录。调优前先看 EXPLAIN 和 INFORMATION_SCHEMA.INNODB_TRX。










