了解完mvcc会不会有这样的疑问:mvcc实现了读不阻塞写,写不阻塞读的高效并发控制,写的时候读是不是拿到实时数据,读的时候可能拿到的不是实时写的数据?
关键区分:两种读操作
mysql innodb 实际上有两种读模式,mvcc 只适用于其中一种:
| 读类型 | 名称 | 实现方式 | 是否阻塞写 | 数据一致性 |
|---|---|---|---|---|
| 快照读 | snapshot read (consistent read) | mvcc,读历史版本 | 不阻塞 | 事务开始时的快照 |
| 当前读 | current read (locking read) | 加锁读最新版本 | 会阻塞 | 最新已提交数据 |
-- 快照读(普通 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:读历史版本就够了(大多数场景)
银行查询账户余额(只读场景):
事务a(转账):update account set balance = 900 where id = 1; -- 扣100
事务b(查询):select balance from account where id = 1; -- 读快照├─► 事务b 读到的 1000元 是"过时"的吗?技术上是的
├─► 但这是事务b 启动时的准确快照,业务上完全合理
└─► 用户只是查余额,不需要看到实时变化(实时变化可能还在处理中)
场景2:必须读最新数据(需要当前读)
库存扣减(不能超卖):
事务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提交,但保证了业务正确性
一致性模型对比
┌─────────────────────────────────────────────────────────────┐ │ 一致性光谱 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 最终一致性 ◄────────────────────────────► 强一致性 │ │ │ │ 快照读(mvcc) 当前读(加锁) │ │ ├─ 读历史版本 ├─ 读最新版本 │ │ ├─ 可能"过时"但自洽 ├─ 实时但可能阻塞 │ │ └─ 适合:报表、查询 └─ 适合:金融交易、库存 │ │ │ └─────────────────────────────────────────────────────────────┘
为什么 mvcc 这样设计?
┌─────────────────────────────────────────────────────────┐ │ 数据库设计哲学:不同场景需要不同的一致性级别 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 场景1:淘宝商品列表页 │ │ ├─ 100万人同时浏览 │ │ ├─ 库存数字差几个没关系 │ │ └─ 用 mvcc 快照读 → 高并发,不阻塞商家改库存 │ │ │ │ 场景2:下单扣库存 │ │ ├─ 必须精确知道当前库存 │ │ ├─ 不能超卖 │ │ └─ 用当前读 for update → 阻塞等待,保证正确性 │ │ │ │ 同一系统,两种策略,各取所需 │ │ │ └─────────────────────────────────────────────────────────┘
代码示例对比
-- 假设表: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 同时提供两种机制,而不是一刀切。
到此这篇关于mysql从零单排之快照读与当前读的实现的文章就介绍到这了,更多相关mysql 快照读与当前读内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论