MySQL MVCC(多版本并发控制) 。
这个知识点刚开始会比较抽象,但是你前面已经理解了:
- Java锁
- 数据库行锁
- 事务
- 乐观控制
所以现在会容易很多。
一、先回顾一个问题
我们之前说:
普通查询:
select *
from asset_warrant_info
where id=10001;
不会加锁。
那问题来了:
如果两个事务同时操作:
怎么办?
例如:
数据库:
订单
id=10001
status=待支付
事务A:
begin;
select *
from orders
where id=10001;
看到:
待支付
事务B:
同时:
begin;
update orders
set status='已支付'
where id=10001;
commit;
这时候事务A再次查询:
会看到什么?
如果直接读取最新数据:
第一次:
待支付
第二次:
已支付
同一个事务里面看到的数据变化了。
这叫:
不可重复读
所以MySQL需要一种机制:
让事务中的查询结果保持一致。
这就是:
MVCC
二、MVCC是什么?
全称:
Multi-Version Concurrency Control
中文:
多版本并发控制
简单理解:
MySQL不会直接覆盖旧数据。
而是:
保存多个版本。
例如:
订单:
最开始:
status=待支付
版本1。
后来:
事务B修改:
status=已支付
MySQL内部:
类似:
版本2:
status=已支付
版本1:
status=待支付
旧版本不会立即删除。
所以:
事务A:
查询的时候:
可以看到:
适合自己的那个版本。
三、MVCC解决什么问题?
核心:
读不加锁,提高并发。
如果没有MVCC:
读取:
必须:
select
|
加锁
|
读取
这样:
大量查询会互相等待。
有MVCC:
读取:
select
|
找历史版本
|
返回
不用锁。
四、这个和ConcurrentHashMap有什么相似?
你之前问:
ConcurrentHashMap为什么get不用加锁?
这里很像。
ConcurrentHashMap:
写:
需要控制。
读:
尽量无锁。
MySQL:
修改:
需要锁。
查询:
利用MVCC:
不用锁。
思想:
都是:
读多写少场景,提高并发。
五、MySQL怎么保存这些版本?
InnoDB靠两个东西:
1. 隐藏字段
每一行数据实际上有一些隐藏字段:
例如:
用户表
id
name
实际上类似:
id
name
trx_id
roll_pointer
trx_id
表示:
哪个事务修改了这条数据。
例如:
事务100修改:
name=张三
记录:
trx_id=100
roll_pointer
指向旧版本。
像链表:
当前版本
↓
旧版本
↓
更旧版本
六、undo log在这里发挥作用
之前讲:
undo log用于回滚。
但是它还有一个作用:
保存历史版本。
例如:
原数据:
余额=1000
事务A:
执行:
update account
set balance=900;
数据库:
当前:
balance=900
undo log:
保存:
balance=1000
如果其他事务需要看旧数据:
从undo log找。
七、事务隔离级别和MVCC关系
MySQL有4种隔离级别:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
我们重点看两个。
1. READ COMMITTED(读已提交)
意思:
只能看到别人已经提交的数据。
例如:
事务A:
修改:
余额=900
但是没commit。
事务B:
查询:
看不到。
等A:
commit。
B再次查询:
看到:
900。
2. REPEATABLE READ(可重复读)
MySQL默认。
特点:
同一个事务:
多次查询:
结果一致。
例如:
事务A开始:
时间点T1
查询:
余额1000
事务B修改:
余额900
commit
事务A再次查询:
仍然:
余额1000
因为:
MVCC找到旧版本。
八、那什么时候会看到最新数据?
比如:
你的事务:
begin;
select *
from account
where id=1;
普通select:
走MVCC。
但是:
如果:
select *
from account
where id=1
for update;
这个不同。
它不是快照读。
它是:
当前读。
意思:
我要最新真实数据。
所以:
需要加锁。
九、两种读取方式
这个非常重要。
1. 快照读
普通:
select *
from table;
特点:
- 不加锁
- 使用MVCC
- 读取历史版本
2. 当前读
例如:
select *
from table
for update;
或者:
update
delete
insert
特点:
- 读取最新数据
- 需要锁
十、结合你的审批场景
比如:
审批页面查询:
select *
from asset_warrant_info
where id=10001;
这是:
快照读。
用户查看资料:
不需要锁。
点击:
“提交审批”:
执行:
update asset_warrant_info
set status='审批中'
where id=10001
and status='待补充';
这是:
当前修改。
MySQL:
加锁。
所以一个业务流程:
可能同时用了:
查询页面
|
| MVCC
|
展示数据
提交按钮
|
| 行锁
|
修改状态
十一、面试怎么回答MVCC?
如果问:
MySQL如何实现高并发读取?
可以回答:
InnoDB通过MVCC实现非阻塞读。每条记录内部维护事务版本信息,并结合undo log保存历史版本。普通select通过Read View判断可见性,读取符合事务隔离级别的数据,而update、delete等写操作通过行锁保证并发修改安全。