为什么MySQL读未提交会产生脏读?

作者:袖梨 2026-08-28

读未提交(READ UNCOMMITTED)允许读到未提交数据,因其完全不加读锁、不依赖一致性视图,SELECT直接读取最新行版本且不做trx_id可见性判断;脏读本质是读到后续可能回滚的无效数据,如事务A更新后未提交即被事务B读取并误用,A回滚后B所持数据即为“脏数据”。

读未提交(READ UNCOMMITTED)为什么允许读到未提交的数据

因为该隔离级别完全不加读锁,也不依赖一致性视图(consistent view),SELECT语句直接读取最新写入的行版本,不管它是否属于已提交事务。InnoDB 的行记录里有隐藏字段 trx_id,但在此级别下,引擎不做 trx_id 可见性判断——只要物理数据存在,就返回。

脏读发生的典型链路

关键不是“能不能读”,而是“读到了不该信的结果”。常见触发路径如下:

  1. 事务 A 执行 UPDATE account SET balance = 800 WHERE id = 1,但卡在 COMMIT
  2. 事务 B 在同一时刻执行 SELECT balance FROM account WHERE id = 1
  3. 事务 B 立刻拿到 800,并可能基于此做下游决策(如发货、放款)
  4. 事务 A 随后执行 ROLLBACK,数据库实际余额回到 1000
  5. 事务 B 持有的 800 成为无效状态,即“脏数据”

为什么其他级别能避免脏读

区别在于是否引入“提交可见性检查”:

  1. READ COMMITTED:每次 SELECT 都生成新的快照,只包含已提交事务的修改
  2. REPEATABLE READ:事务启动时固定一个快照,后续所有读都基于它,跳过未提交变更
  3. SERIALIZABLE:对读操作加 gap locknext-key lock,强制串行化

READ UNCOMMITTED 连最基础的提交检查都跳过,等于把 MVCC 机制关了一半。

实际中几乎没人用,但调试时容易误设

生产环境基本不会显式设成 READ UNCOMMITTED,但要注意两种隐性风险:

  1. 开发本地 MySQL 实例可能被手动执行过 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,后续会话沿用
  2. 某些 ORM(如旧版 Django)或连接池配置错误,导致全局默认隔离级别被降级

查当前会话级别用 SELECT @@tx_isolation;查全局默认用 SELECT @@global.transaction_isolation。一旦发现是 READ-UNCOMMITTED,得立刻追查来源——这不是性能优化,是数据安全缺口。

相关文章

精彩推荐