一、事务隔离解决什么问题
数据库事务要同时处理一致性和并发。隔离级别定义一个事务能看到其他事务修改到什么程度。隔离越强,并发现象越少,但锁竞争和性能成本可能更高。MySQL InnoDB 默认隔离级别是 Repeatable Read,它通过 MVCC 和锁机制平衡读写。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 |
| Read Committed | 避免 | 可能 | 可能 |
| Repeatable Read | 避免 | 避免 | 需机制处理 |
| Serializable | 避免 | 避免 | 避免 |
理解隔离级别时,不要只背定义,要结合快照读、当前读和锁来看。
二、MVCC的基本思路
MVCC 即多版本并发控制。InnoDB 每行记录背后有隐藏字段,配合 undo log 保存历史版本。普通 SELECT 是快照读,读取事务开始时或语句开始时可见的数据版本,不阻塞正在修改的事务。
sql
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 其他事务更新并提交
SELECT balance FROM account WHERE id = 1;
COMMIT;
在 Repeatable Read 下,同一事务中两次快照读通常看到相同结果;在 Read Committed 下,每条语句会生成新的 read view,第二次可能看到其他事务已提交的数据。这就是不可重复读差异的来源。
三、快照读与当前读
不是所有读都走 MVCC 快照。SELECT ... FOR UPDATE、UPDATE、DELETE 属于当前读,要读取最新已提交版本,并可能加锁。很多线上锁等待来自把普通查询改成了当前读,或者在事务里长时间持有锁。
sql
START TRANSACTION;
SELECT * FROM orders
WHERE user_id = 1001
FOR UPDATE;
UPDATE orders
SET status = 'PAID'
WHERE id = 9001;
COMMIT;
当前读需要合适索引。如果条件没有命中索引,InnoDB 可能扫描更多记录并加更多锁,导致并发下降。事务中的 SQL 越精确,锁范围越可控。
四、生产中的隔离级别选择
大多数业务使用默认 Repeatable Read 可以满足需求。如果希望每条语句都看到最新提交数据,并减少某些间隙锁影响,可以评估 Read Committed,但要确认业务是否依赖可重复读语义。
sql
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
事务设计建议:事务尽量短,避免在事务中调用外部接口;按固定顺序更新资源,降低死锁概率;所有更新条件使用索引;捕获死锁后对幂等操作做有限重试。MVCC 让读写并发更高,但它不能替代良好的事务边界和索引设计。
还要理解长事务的危害。长事务会让旧版本数据无法及时清理,undo log 膨胀,影响查询和磁盘空间。后台任务如果一次处理大量数据,应分批提交,并记录进度。线上排查时可以查看当前事务、锁等待和执行时间,优先处理持锁久、扫描多的语句。隔离级别是数据库能力,业务仍然要通过幂等、唯一约束和状态机保证最终一致。
在读写分离架构中,还要考虑复制延迟。刚写入主库后立刻读从库,可能读不到最新数据,需要按业务选择读主库或做延迟保护。
📌 本文是《数据库性能实战》系列,持续更新,关注不迷路。
👉 下一篇:《数据库连接池耗尽排查与兜底》,讲事务、慢查询与连接归还如何共同引发池耗尽。
💬 你在实际项目里遇到过长事务、锁等待或刚写完却读不到数据的问题吗?评论区聊聊。
(觉得有用点个赞+收藏,方便回头查阅)
🔧 相关可运行源码/资料已整理成资源包,可在我主页的资源里自取。