脏读只会在read uncommitted隔离级别下发生;该级别允许事务读取其他事务未提交的数据,而read committed、repeatable read和serializable均通过mvcc或锁机制避免脏读。

脏读到底在什么隔离级别下会发生
脏读只会在 READ UNCOMMITTED 隔离级别下发生。其他三个级别(READ COMMITTED、REPEATABLE READ、SERIALIZABLE)都通过不同机制阻止了脏读——不是靠“锁住所有东西”,而是靠 MVCC(多版本并发控制)或行锁/间隙锁的组合。
mysql 默认是 REPEATABLE READ,所以只要没显式改过隔离级别,脏读就基本不会出现。但要注意:有些 ORM 或连接池初始化时会执行 SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,这种配置一开,脏读风险立刻回来。
如何确认当前会话的隔离级别
别猜,直接查。MySQL 提供两个关键变量:
-
@@tx_isolation(5.7.20+ 已弃用,但多数环境仍可读) -
@@transaction_isolation(推荐,8.0+ 唯一标准)
执行以下命令即可:
select @@transaction_isolation;
返回值类似 'REPEATABLE-READ' 或 'READ-COMMITTED'。注意中间是短横线,不是下划线,拼错会报错。
如果看到 'READ-UNCOMMITTED',说明当前会话已主动降级,需检查应用层是否调用了 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。
READ COMMITTED 和 REPEATABLE READ 的关键区别在哪
两者都不允许脏读,但对“不可重复读”和“幻读”的处理逻辑完全不同,直接影响你写业务逻辑时要不要加 SELECT ... for UPDATE:
-
READ COMMITTED:每次SELECT都生成新快照,能读到其他事务已提交的最新数据 → 可能出现不可重复读 -
REPEATABLE READ(InnoDB 默认):事务启动时创建一致性视图(consistent read view),后续所有普通SELECT都复用该视图 → 不可重复读被屏蔽,但幻读仍可能在特定场景下发生(如未加锁的范围查询)
举例:你在事务中两次执行 SELECT count(*) FROM orders WHERE status = 'pending',若中间有其他事务插入并提交了一条新订单,在 READ COMMITTED 下第二次结果会变;在 REPEATABLE READ 下结果不变——但这不意味着幻读完全消失,UPDATE / delete 的 where 条件若匹配新增行,仍可能触发 gap lock 或 next-key lock。
并发更新时仅靠隔离级别不够,得配合锁语句
隔离级别解决的是“读一致性”,不是“写冲突”。比如两个事务同时执行:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
即使在 REPEATABLE READ 下,也**不会自动加行锁防止覆盖写**——InnoDB 确实会对匹配行加 X 锁,但前提是 WHERE 条件命中索引。如果 id 没建索引,会退化为表锁,性能崩盘;如果条件走不到索引(比如 WHERE JSON_CONTAINS(data, '"admin"')),那锁行为就不可控。
真正安全的做法是显式加锁:
- 读后再更新:先
SELECT ... FOR UPDATE,再做计算和UPDATE - 原子更新:直接用
UPDATE ... SET x = x + 1 WHERE ...,依赖 InnoDB 的当前读机制 - 避免长事务:
REPEATABLE READ下长事务会拖住 purge 线程,导致 undo log 膨胀,最终撑爆ibdata1
最常被忽略的一点:SELECT ... FOR UPDATE 在唯一索引等值查询时只锁匹配行;但在非唯一索引或范围查询时,会锁住间隙(gap lock),甚至锁住前后相邻的索引项(next-key lock)——这既是防止幻读的手段,也是死锁高发区。