MySQL默认隔离级别REPEATABLE READ可防脏读和不可重复读,但不自动防幻读;防幻读依赖临键锁而非MVCC,SERIALIZABLE虽彻底避免幻读却严重降低性能。

MySQL 默认用的是可重复读,但不是所有场景都适合
MySQL InnoDB 引擎默认事务隔离级别是 REPEATABLE READ,它能防止脏读和不可重复读,但**不自动阻止幻读**——这点很多人误以为“默认最安全”,结果在分页查询、范围条件更新时踩坑。比如你执行 SELECT * FROM orders WHERE status = 'pending' FOR UPDATE,另一个事务仍可能插入新 pending 订单,导致你后续统计或处理漏掉这条记录。
真正能彻底避免幻读的只有 SERIALIZABLE,但它靠加锁串行化执行,吞吐量断崖式下降,线上基本不用。所以实际选型得看业务容忍度:
- 金融类强一致性场景:考虑 READ COMMITTED + 显式加锁(如 SELECT ... FOR UPDATE)
- 普通电商订单/库存:REPEATABLE READ 足够,但范围操作必须搭配 SELECT ... FOR UPDATE 或用唯一约束兜底
- 日志、埋点等弱一致性场景:READ COMMITTED 更轻量,避免长事务阻塞
MVCC 不是万能的,只对纯 SELECT 有效
MVCC(多版本并发控制)是 MySQL 实现高并发读的关键机制,但它有明确边界:只作用于不带锁的普通 SELECT。一旦你写 SELECT ... LOCK IN SHARE MODE 或 SELECT ... FOR UPDATE,InnoDB 就会退化为加锁读,MVCC 版本快照失效,直接走当前最新已提交版本(READ COMMITTED 行为)或阻塞等待(REPEATABLE READ 下的临键锁)。
常见误用:
- 在事务中先查余额(MVCC 快照),再 UPDATE 扣款,以为两次看到的是同一快照 → 实际 UPDATE 会基于最新行版本加锁,可能触发死锁或幻读
- 用 SELECT ... FOR UPDATE 查范围却没加索引 → 锁升级为表级锁,拖垮整个表性能
- 以为 MVCC 能解决所有并发问题,忽略 INSERT 和 DELETE 本身不走 MVCC,而是直接操作当前版本行
间隙锁和临键锁,是防幻读的真正主力
在 REPEATABLE READ 下,InnoDB 防幻读不靠 MVCC,而是靠 **临键锁(Next-Key Lock)**——即「记录锁 + 间隙锁」的组合。例如 SELECT * FROM accounts WHERE id = 5 FOR UPDATE,实际锁住的是 id=5 的记录,以及 (1,5) 这个左开右闭的间隙,防止其他事务插入 id=2、3、4 的新行。
关键细节:
- 间隙锁只在**有索引的列**上生效;全表扫描或无索引字段查询会锁整张表
- READ COMMITTED 级别下,间隙锁被禁用,只保留记录锁 → 幻读风险真实存在
- 唯一索引等值查询(如 WHERE id = 5)会降级为纯记录锁,不锁间隙;但范围查询(WHERE id > 5)或非唯一索引一定会触发间隙锁
- INSERT 操作会触发隐式加锁,即使没写 FOR UPDATE,也可能被间隙锁阻塞
查隔离级别和锁状态,不能只看文档,得看实时数据
开发时别光信配置文件或文档描述,务必用系统表验证实际行为。比如你以为设了 READ COMMITTED,但某个连接可能被框架或中间件悄悄改过:
SELECT @@transaction_isolation; —— 查当前会话级别SELECT * FROM information_schema.INNODB_TRX; —— 看活跃事务及其状态、开始时间、SQLSELECT * FROM information_schema.INNODB_LOCK_WAITS; —— 查谁在等锁、等谁的锁SELECT * FROM information_schema.INNODB_LOCKS; —— 查当前持有的锁(注意:8.0+ 已废弃,需用 performance_schema.data_locks 替代)
最容易被忽略的一点:**事务未显式 START TRANSACTION 时,每个 autocommit=1 的单条 SQL 也是一个独立事务**。这意味着你在命令行里随手敲的 UPDATE,哪怕没写 BEGIN,也受当前隔离级别约束,且锁会立即释放——这和 ORM 中长事务的行为完全不同。










