脏读在 READ UNCOMMITTED 下一定会发生,因为该隔离级别完全不加读锁也不检查写锁,SELECT 可直接读到其他事务未 COMMIT 的修改,如事务 A 更新某行未提交,事务 B 此时 SELECT 即可读到脏值。

脏读在 READ UNCOMMITTED 下为什么一定会发生
因为该隔离级别完全不加读锁,也不检查写锁,SELECT 可以直接读到其他事务尚未 COMMIT 的修改。比如事务 A 更新了某行但未提交,事务 B 此时执行 SELECT 就能拿到这个“脏值”。
常见错误现象:ERROR 1205 (40001): Deadlock found when trying to get lock 虽然不直接相关,但很多人误以为开低隔离级就能避免死锁——其实相反,READ UNCOMMITTED 会放大并发写冲突的隐蔽性。
实操建议:
- 仅用于日志表、监控计数等允许误差的只读场景
- MySQL 默认不启用此级别,需显式设置
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED - ORM 框架(如 Django、MyBatis)通常不支持该级别,强行配置可能被忽略
不可重复读和幻读的区别到底在哪
不可重复读(Non-Repeatable Read)指同一事务中两次 SELECT 同一行,结果不一致,原因是其他事务对该行执行了 UPDATE 或 DELETE 并已提交;而幻读(Phantom Read)是两次 SELECT 返回行数不同,原因是其他事务插入(INSERT)或删除了满足条件的新行。
关键区别在于:不可重复读聚焦「已有行内容变化」,幻读聚焦「结果集范围变化」。
MySQL InnoDB 在 REPEATABLE READ 级别下通过间隙锁(Gap Lock)+ 行锁(Record Lock)阻止幻读,但仅对当前读(如 SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE)生效;快照读(普通 SELECT)靠 MVCC 避免不可重复读,但本身不解决幻读——只是你“看不见”新插入的行。
实操建议:
-
SELECT * FROM t WHERE id = 1是快照读,不会阻塞插入;但SELECT * FROM t WHERE id = 1 FOR UPDATE会加临键锁(Next-Key Lock),阻止间隙插入 - 用
EXPLAIN查看执行计划时,若出现type: index且Extra: Using where; Using index,说明走了覆盖索引,但间隙锁范围仍由查询条件决定,不是由是否覆盖决定 - 唯一索引等值查询(如
WHERE id = ?)只锁匹配行,不锁间隙;普通索引或范围查询才触发间隙锁
为什么 MySQL 默认用 REPEATABLE READ 而不是 READ COMMITTED
历史原因:早期 MySQL 主从复制依赖语句级(SBR)模式,READ COMMITTED 下同一事务内多次执行相同语句可能产生不同结果,导致主从不一致;虽然现在默认是行级复制(RBR),但兼容性与行为稳定性让 REPEATABLE READ 保留为默认。
性能影响明显:REPEATABLE READ 需维护更长的 undo log 链(事务开启后第一个快照持续有效),高并发长事务容易撑爆 undo tablespace;而 READ COMMITTED 每次快照读都取最新已提交版本,undo 压力小。
实操建议:
- 高并发短事务(如 API 请求)可考虑全局设为
READ COMMITTED,降低锁竞争和 undo 开销 - 变更隔离级别前务必测试 binlog 格式:
SHOW VARIABLES LIKE 'binlog_format'应为ROW,否则 SBR +READ COMMITTED仍有风险 - Spring
@Transactional(isolation = Isolation.READ_COMMITTED)在 MySQL 上生效,但要注意连接池是否复用旧连接并携带旧隔离级
Serializable 怎么“彻底”解决所有并发问题
SERIALIZABLE 会让所有普通 SELECT 自动转成 SELECT ... LOCK IN SHARE MODE,即每次读都加共享锁,写操作则需排他锁,本质上把并发事务串行化执行。
这确实消灭了脏读、不可重复读、幻读,但也带来严重代价:并发度归零倾向,极易触发锁等待甚至死锁。例如两个事务同时执行 SELECT * FROM t WHERE status = 0,再各自尝试 UPDATE 其中某行,就会因共享锁互斥而卡住。
实操建议:
- 不要在业务代码里动态设为
SERIALIZABLE,它更适合离线数据校验脚本或极低频管理后台操作 - 若真需要强一致性,优先考虑应用层加分布式锁(如 Redis SETNX)或用乐观锁(
version字段 +WHERE version = ?)替代 -
SHOW ENGINE INNODB STATUS\G中的TRANSACTIONS段会显示当前持有哪些锁,SERIALIZABLE下很容易看到大量LOCK_MODE: S和LOCK_TYPE: RECORD条目
事务隔离级别的选择从来不是“越严越好”,而是权衡一致性需求、性能损耗和锁行为复杂度。最容易被忽略的是:MVCC 快照读的可见性规则与锁机制是两套并行逻辑,混用时(比如一边快照读、一边 FOR UPDATE)容易误判“为什么没锁住”。










