你以为你设置了“读已提交”就万事大吉?别天真了,真正的脏读陷阱藏在你没注意的细节里。
脏读是怎么发生的?
说白了,脏读就是:你看到的数据,根本不是别人改完的。
比如你查一个账户余额,刚看到1000元,转头发现人家已经改成了999,但还没commit——这不就是“假账”吗?
MySQL默认的隔离级别是可重复读(Repeatable Read),它虽然解决了脏读,但也会引发新的问题,比如幻读。
所以,如果你的业务逻辑要求绝对数据一致性,那就得手动调低隔离级别,或者用更高级别的控制手段。
配置一:把隔离级别改成 Read Committed
这是最常见也最直接的解决方案。
原理拆解:
- 在
Read Committed下,每个事务只能看到已经提交的数据。 - 所以当你查数据时,如果另一个事务正在改,你会看到旧值,而不是中间态。
- 这种方式对大多数应用来说“够用”,代价是性能略有下降。
实战步骤:
-- 设置当前会话的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 然后执行你的查询
SELECT balance FROM accounts WHERE user = '小明';
⚠️ 注意:
READ COMMITTED不能完全杜绝幻读,但可以解决脏读。
对比表:不同隔离级别的表现
| 隔离级别 | 是否脏读 | 是否不可重复读 | 是否幻读 |
|---|---|---|---|
| Read Uncommitted | ✅ 是 | ✅ 是 | ✅ 是 |
| Read Committed | ❌ 否 | ✅ 是 | ✅ 是 |
| Repeatable Read | ❌ 否 | ❌ 否 | ✅ 是 |
| Serializable | ❌ 否 | ❌ 否 | ❌ 否 |
配置二:启用 MVCC + 快照读机制
这是MySQL InnoDB存储引擎的核心技术,很多人以为“读已提交”就能解决问题,其实背后靠的是MVCC。
深层逻辑:
- InnoDB为每条记录保存两个隐藏字段:
row_trx_id和roll_pointer - 当事务开始时,系统生成一个“视图”(read view),所有查询都基于这个视图返回数据
- 即使有其他事务在修改数据,只要没提交,你看到的就是“快照”
实战案例:某电商平台订单系统误扣款事件
某次促销活动期间,系统频繁更新库存,A事务读取商品数量为100,B事务同时在减库存至98,但B还没提交。
此时A读到的是100,却下单买了101件——直接导致超卖。
🔥 正确做法是开启
READ COMMITTED并配合SELECT ... FOR UPDATE锁定资源,防止并发写入。
配置三:使用 BEGIN WORK AND CHAIN 控制事务链
很多开发者对事务链的理解停留在“自动提交”层面,其实它还有个关键功能——事务之间状态继承。
使用场景举例:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user = '小明';
COMMIT WORK AND CHAIN; -- 提交后继续下一个事务
🧠 关键点在于:
CHAIN让下一次事务保持相同的隔离级别和锁策略。
避坑指南①:别把“读已提交”当成万能钥匙!
很多人觉得只要设置成READ COMMITTED就高枕无忧,其实不然。
如果你的应用对一致性要求极高(比如金融支付),那必须使用SERIALIZABLE,哪怕性能牺牲一点。
案例复盘:某银行核心交易系统因脏读引发资金错乱
某银行后台系统因未正确设置事务隔离级别,导致用户转账时出现“先扣款再到账”的异常现象。
具体流程如下:
- 用户A发起一笔转账给B
- A账户余额被锁定,准备扣款
- B账户正在被其他线程更新余额(未提交)
- 因为事务隔离是
REPEATABLE READ,B账户看起来还是老数据 - 结果A扣了钱,B没收到,最后审计发现“钱凭空消失了”
💥 最终排查发现:应该强制使用
READ COMMITTED+ 显式加锁来规避此问题。
FAQ:关于事务隔离的那些坑
Q1:为什么我明明设置了READ COMMITTED,还是能看到脏数据?
A:你可能用了SELECT ... FOR UPDATE或LOCK IN SHARE MODE,这些操作会升级锁粒度,影响可见性。
记住一句话:锁住的是索引,不是整个表。
Q2:可重复读真的能防止所有并发问题吗?
A:不行。它能防脏读和不可重复读,但无法阻止幻读。
如果你需要防止幻读,请使用SERIALIZABLE,但代价是性能急剧下降。
Q3:我能不能全局改transaction_isolation参数?
A:可以,但不推荐。
因为不同模块对数据一致性的要求不一样,强行统一反而带来风险。
Q4:有没有办法动态切换隔离级别而不重启服务?
A:可以,通过SET SESSION临时修改即可。
不过要注意,会话级设置只作用于当前连接,不适合长期部署。
总结一句:
别再迷信默认配置了。
脏读不是技术难题,而是你对事务理解不到位。
搞清楚MVCC、快照读、锁机制,你才能真正掌控数据库的“读”与“写”。
想知道怎么在高并发下稳定运行事务?下期我来教你:如何用Next-Key Lock构建高可用读写分离架构。