脏读仅发生在READ UNCOMMITTED级别,需同时满足事务B修改未提交、事务A在该级别下读取;InnoDB实际将其降级为READ COMMITTED,真脏读多见于直连客户端手动设级或MyISAM引擎。

MySQL中脏读只可能发生在 READ UNCOMMITTED 级别,但实际排查时你会发现:即使没显式设置该级别,也可能出现“类脏读”现象——根本原因是事务隔离级别配置不一致 + 未提交事务长期挂起。
确认当前会话与全局隔离级别是否匹配
不同连接可能继承不同默认值,尤其在使用连接池或ORM时容易被覆盖。执行以下命令检查真实生效级别:
SELECT @@session.transaction_isolation, @@global.transaction_isolation;
常见陷阱:
-
@@session.transaction_isolation显示REPEATABLE-READ,但应用代码里执行了SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED却没报错(InnoDB 实际按READ COMMITTED处理,但部分中间件或旧驱动可能误传) - 某些云数据库(如阿里云RDS)默认关闭
innodb_strict_mode,导致SET命令静默失败 - Spring Boot 的
@Transactional(isolation = Isolation.READ_UNCOMMITTED)在 MySQL 上不会真正启用脏读,但开发者误以为它生效了
复现脏读必须满足的两个硬条件
仅靠隔离级别声明不够,必须同时满足:
- 事务B执行了
UPDATE或INSERT,但尚未COMMIT或ROLLBACK - 事务A在
READ UNCOMMITTED下执行SELECT(注意:InnoDB 实际会降级为READ COMMITTED,所以真脏读往往来自直连客户端手动设级 + 未提交事务)
验证方法:开两个终端,A中执行:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; SELECT * FROM goods_stock WHERE id = 1;
B中执行:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
START TRANSACTION; UPDATE goods_stock SET stock = 50 WHERE id = 1; -- 先不 COMMIT
此时A再次查询,若看到 stock = 50,即确认脏读发生;若仍为原值,则说明实际未进入 READ UNCOMMITTED 模式。
查出正在运行的未提交事务
脏读的根源是“活着的未提交事务”,用以下语句定位:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started < NOW() - INTERVAL 10 SECOND;
重点关注:
-
trx_query是否包含未完成的UPDATE/DELETE(尤其是没有WHERE条件的) -
trx_started时间过长(>30秒)的事务,大概率是应用层异常中断后残留 -
trx_mysql_thread_id可用于后续KILL,但需先确认业务影响
为什么线上几乎看不到纯脏读,却总遇到“疑似脏读”?
真正原因不是隔离级别设错,而是:
- 应用逻辑把“读到已提交但非最新”的数据当成了脏读(其实是
READ COMMITTED下的正常快照行为) - 监控脚本或定时任务在事务未结束时读取中间状态(比如转账中余额暂扣未更新)
- 跨库/跨服务调用时,MySQL 隔离级别对其他系统无效,造成感知错乱
最易被忽略的一点:READ UNCOMMITTED 在 InnoDB 中形同虚设,但开发测试环境用 MyISAM 引擎时可能真触发脏读——而你根本没意识到引擎差异。

















