mysql从零单排之快照读与当前读

了解完MVCC会不会有这样的疑问:MVCC实现了读不阻塞写,写不阻塞读的高效并发控制,写的时候读是不是拿到实时数据,读的时候可能拿到的不是实时写的数据?

关键区分:两种读操作

MySQL InnoDB 实际上有两种读模式,MVCC 只适用于其中一种:

读类型 名称 实现方式 是否阻塞写 数据一致性
快照读 Snapshot Read (Consistent Read) MVCC,读历史版本 不阻塞 事务开始时的快照
当前读 Current Read (Locking Read) 加锁读最新版本 会阻塞 最新已提交数据
sql 复制代码
-- 快照读(普通 SELECT)-- 使用 MVCC
SELECT * FROM user WHERE id = 1;

-- 当前读(加锁 SELECT)-- 不使用 MVCC,直接读最新版本
SELECT * FROM user WHERE id = 1 FOR UPDATE;   -- 排他锁
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;  -- 共享锁

你的问题:

答案取决于业务场景:

场景1:读历史版本就够了(大多数场景)

sql 复制代码
银行查询账户余额(只读场景):

事务A(转账):UPDATE account SET balance = 900 WHERE id = 1;  -- 扣100
事务B(查询):SELECT balance FROM account WHERE id = 1;        -- 读快照

├─► 事务B 读到的 1000元 是"过时"的吗?技术上是的
├─► 但这是事务B 启动时的准确快照,业务上完全合理
└─► 用户只是查余额,不需要看到实时变化(实时变化可能还在处理中)

场景2:必须读最新数据(需要当前读)

sql 复制代码
库存扣减(不能超卖):

事务A:UPDATE stock SET count = count - 1 WHERE id = 1;  -- 剩9件
事务B:SELECT count FROM stock WHERE id = 1 FOR UPDATE;   -- 必须知道最新值!

├─► 事务B 用 FOR UPDATE,强制读最新版本(当前读)
├─► 如果 count 已经是 0,事务B 就知道卖完了
└─► 这会阻塞直到事务A提交,但保证了业务正确性

一致性模型对比

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                    一致性光谱                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  最终一致性 ◄────────────────────────────► 强一致性          │
│                                                             │
│  快照读(MVCC)                    当前读(加锁)                │
│  ├─ 读历史版本                    ├─ 读最新版本               │
│  ├─ 可能"过时"但自洽               ├─ 实时但可能阻塞          │
│  └─ 适合:报表、查询               └─ 适合:金融交易、库存    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

为什么 MVCC 这样设计?

sql 复制代码
┌─────────────────────────────────────────────────────────┐
│  数据库设计哲学:不同场景需要不同的一致性级别               │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  场景1:淘宝商品列表页                                   │
│  ├─ 100万人同时浏览                                     │
│  ├─ 库存数字差几个没关系                                  │
│  └─ 用 MVCC 快照读 → 高并发,不阻塞商家改库存              │
│                                                         │
│  场景2:下单扣库存                                       │
│  ├─ 必须精确知道当前库存                                  │
│  ├─ 不能超卖                                            │
│  └─ 用当前读 FOR UPDATE → 阻塞等待,保证正确性             │
│                                                         │
│  同一系统,两种策略,各取所需                              │
│                                                         │
└─────────────────────────────────────────────────────────┘

代码示例对比

sql 复制代码
-- 假设表:account(id, balance)

┌─────────────────────────────────────────────────────────────┐
│  事务A(转账)                                                │
├─────────────────────────────────────────────────────────────┤
│  START TRANSACTION;                                          │
│  -- 扣减转账账户                                              │
│  UPDATE account SET balance = balance - 100 WHERE id = 1;    │
│  -- 此时 id=1 的 balance 内存中有新版本,但未提交              │
│                                                              │
│  -- 做一些其他处理(耗时较长)                                  │
│  ...                                                         │
│                                                              │
│  COMMIT;                                                     │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ 并发执行
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  事务B(查询余额)- 快照读                                     │
├─────────────────────────────────────────────────────────────┤
│  START TRANSACTION;                                          │
│  SELECT balance FROM account WHERE id = 1;  -- 读快照        │
│  -- 结果:1000(事务A修改前的值)                             │
│  -- 原理:TRX_ID 判断,事务A未提交,对事务B不可见               │
│  COMMIT;                                                     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  事务C(必须确认转账结果)- 当前读                              │
├─────────────────────────────────────────────────────────────┤
│  START TRANSACTION;                                          │
│  SELECT balance FROM account WHERE id = 1 FOR UPDATE;        │
│  -- 结果:等待... 直到事务A提交后返回 900                      │
│  -- 原理:加排他锁,强制读最新版本,阻塞等待                    │
│  COMMIT;                                                     │
└─────────────────────────────────────────────────────────────┘

总结

你的顾虑 MVCC 的答案
「写的时候读,数据不准确」 快照读 故意读"旧"数据,换取不阻塞
「但我需要准确数据」 当前读 (FOR UPDATE),牺牲并发换准确
「到底准不准」 快照读在事务开始时是一致的、自洽的,只是不是最新的

MVCC 不是万能药,而是给开发者选择权:

要并发?用快照读。要准确?用当前读。

这也是为什么 InnoDB 同时提供两种机制,而不是一刀切。

相关推荐
Leo8992 小时前
mysql从零单排之B+与AHI
后端
hresh2 小时前
两个 Chrome 窗口各 20 多个 tab 后,我把 tab-out 改成了更顺手的 TabNest
前端·chrome·后端
彭于晏Yan2 小时前
Spring Boot 整合 WebSocket 实现单聊+广播
spring boot·后端·websocket
武子康2 小时前
大数据-275 Spark MLib-集成学习:从Bagging到Boosting的群体智慧
大数据·后端·spark
SimonKing2 小时前
国产开源富文本编辑器 wangEditor,本姓编辑器
java·后端·程序员
Moment2 小时前
面试官:LangChain中 TS 和 Python 版本有什么差别,什么时候选TS ❓❓❓
前端·javascript·后端
ATCH IERV2 小时前
如何在 Spring Boot 中配置数据库?
数据库·spring boot·后端
IT_陈寒2 小时前
React状态管理这个坑,我终于爬出来了
前端·人工智能·后端
Mr -老鬼2 小时前
Salvo Web框架专属AI智能体 - 让Rust开发效率翻倍
人工智能·后端·rust·智能体·salvo