脏读到底在什么隔离级别下会发生
脏读只会在
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)——这既是防止幻读的手段,也是死锁高发区。
