5.2 隔离病房:隔离级别与MVCC


5.2 隔离病房:隔离级别与 MVCC

本节摘要:隔离级别决定并发事务能看到彼此多少中间状态。InnoDB 默认可重复读,靠 MVCC 让读不加锁。本节用两个会话复现脏读与幻读的差别,并解释快照读与当前读的分野。

四个级别治三种病

隔离级别 脏读 不可重复读 幻读
读未提交 可见 可见 可见
读已提交 挡住 可见 可见
可重复读(默认) 挡住 挡住 基本挡住
串行化 挡住 挡住 挡住

三种病的区别一句话:脏读是看到别人没提交的草稿;不可重复读是同一行两次读结果不同;幻读是同一条件两次读行数变了。

SELECT @@transaction_isolation; SET SESSION transaction_isolation = 'READ-COMMITTED';

很多互联网团队把默认的可重复读改成读已提交——间隙锁更少、并发更高,代价是同事务内两次读可能不同。这是业务能容忍时的合理取舍,不是违规操作。

MVCC:读为什么不加锁

每行藏两个隐藏列:创建版本号与删除版本号(本质是事务号)。读数据时拿自己的快照对比版本号,看得见的版本就返回,看不见的沿着 undo 链往回找旧版本。于是读不阻塞写、写不阻塞读,各看各的"病房视角"。

关键分野在两种读:

-- 快照读:走 MVCC,读旧版本 SELECT * FROM clinic_order WHERE order_id = 88; -- 当前读:读最新版并加锁 SELECT * FROM clinic_order WHERE order_id = 88 FOR UPDATE; UPDATE clinic_order SET status = 3 WHERE order_id = 88; -- 更新也是当前读

幻读的"基本挡住"就出在这里:快照读靠 MVCC 不见新行,但当前读会看见。要彻底挡住,用加锁读或串行化。

图:MVCC 版本链与快照

⚠️ 常见坑:以为 SELECT 不加锁就可以在事务里随便慢慢读。长事务的快照会阻止 undo 清理,旧版本链越拖越长,最终拖垮整库——隔离病房也要按时出院。

亲手复现不可重复读

概念表看十遍不如亲手复现一次。两个会话配合,五分钟看清"可重复读挡住了什么":

-- A 会话 SET SESSION transaction_isolation = 'REPEATABLE-READ'; START TRANSACTION; SELECT balance FROM account WHERE user_id = 1001; -- 第一次读:100 -- B 会话 UPDATE account SET balance = 50 WHERE user_id = 1001; -- (autocommit 下自动提交) -- A 会话再读 SELECT balance FROM account WHERE user_id = 1001; -- 仍是 100,读自己的快照 COMMIT; SELECT balance FROM account WHERE user_id = 1001; -- 50,事务结束回到现实

把隔离级别换成 READ-COMMITTED 重演,A 第二次读直接看到 50。两场对照演完,"快照建立在事务开始时还是每条语句时"这个级别的本质区别就再也不用背了——可重复读的快照建立在第一条快照读时,读已提交每条语句重建快照。

MVCC 的代价:读旧版本不是免费的

版本链是挂在 undo 段上的,链越长,读旧版本要回溯的步数越多。长事务的快照很老,它可能迫使一条被反复更新的行保留几十个旧版本,所有并发读这条行的人都跟着多走几步链。极端情况下 undo 表空间膨胀、purge 线程追不上,整库读写都开始变慢——这不是某个参数坏了,是被一个挂了几天的旧事务拖累:

-- 找出阻止 undo 清理的元凶:最老的活跃快照 SHOW ENGINE INNODB STATUS\G -- History list length 持续增大 = purge 追不上 SELECT trx_id, trx_started FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 3;

History list length 是这个病的体温计,监控面板上值得有它一席之地。

两种读的判断练习

快照读与当前读的区分是本节的考点。判断标准一句话:普通 SELECT 是快照读;带 FOR UPDATE 或 FOR SHARE 的 SELECT、以及所有写语句(UPDATE、DELETE、INSERT 语义上的冲突检查)都是当前读。练习判例:事务里先普通 SELECT 再 UPDATE 同一行,SELECT 看不见别人的修改,UPDATE 却是在别人改后的最新版上动手——两个读的口径不同,这正是某些"读到 A 改成 B"悬疑代码的病根。想统一口径,就在 SELECT 后面加 FOR UPDATE 让它也变成当前读。

级别选择的决策树

隔离级别不是越严越好,拿一棵决策树带走。业务里有"同事务内多次读必须一致"的需求吗(比如对账、报表生成过程)——有,留可重复读;没有,默认考虑读已提交换并发。系统死锁高发且多与间隙锁有关吗——是,读已提交直接消灭这类间隙死锁。基于语句的旧式复制还在用吗——在,必须可重复读以外的选择要谨慎(ROW 格式下无此约束,这也是 8.0 默认 ROW 的原因之一)。全树走完仍两可时,选读已提交:互联网高并发场景的主流实践,锁面小、死锁少,代价(同事务内两次读可能不同)在多数业务里本来就不构成问题。

-- 级别调整是会话级实验、全局级变更,前者随便玩,后者要走变更流程 SET GLOBAL transaction_isolation = 'READ-COMMITTED';

本节要点回顾

  • 三级病症:脏读、不可重复读、幻读逐级挡住,串行化全挡但并发最差
  • MVCC 机制:隐藏版本列加 undo 链,快照读旧版本、当前读最新版
  • 级别取舍:读已提交换并发是常见工程选择

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U