当前位置: 首页 > news >正文

简单易懂的方式理解MVCC--1

简单易懂的方式理解MVCC--1

 

可以。你把 MySQL MVCC 想象成一个非常简单的场景:

“一个商品价格不断修改,但每个顾客看到的是自己应该看到的那个版本。”

这基本就是 MVCC 的核心。


一、先不要管 MVCC,先看一个问题

假设数据库里有一条数据:

 
id = 1
name = iPhone
price = 1000
 

现在有两个事务:

 
事务A:正在查询商品
事务B:正在修改商品价格
 

事务 A 执行:

 
BEGIN;SELECT price FROM product WHERE id = 1;
 

看到:

 
1000
 

这时候事务 B 把价格改成:

 
UPDATE product SET price = 2000 WHERE id = 1;
COMMIT;
 

那么问题来了:

事务 A 再次查询的时候,到底应该看到 1000 还是 2000?

这就是 MVCC 要解决的问题之一。


二、MVCC 可以理解成“给数据拍照片”

这是理解 MVCC 最简单的方法。

假设原来的数据是:

 
price = 1000
 

后来改成:

 
price = 2000
 

再后来:

 
price = 3000
 

MVCC 并不是简单地把旧数据彻底覆盖掉。

而是可以理解成:

 
1000  ← 老版本↓
2000  ← 新版本↓
3000  ← 更新后的版本
 

数据库内部会保留一定的历史版本信息

所以:

 
事务A↓
我要看我应该看到的版本↓
MVCC↓
找到合适的历史版本
 

这就是所谓的:

Multi-Version Concurrency Control

多版本并发控制


三、但是数据库怎么知道“我应该看哪个版本”?

这才是 MVCC 最关键的地方。

MySQL InnoDB 会在记录背后维护一些隐藏信息,例如:

 
DB_TRX_ID
DB_ROLL_PTR
 

你可以先不用纠结名字。

简单理解:

 
DB_TRX_ID↓
“这条数据是哪个事务修改的?”DB_ROLL_PTR↓
“如果我要看以前的版本,可以通过这个指针找到历史版本”
 

于是数据就像形成了一条链:

 
最新版本↓
旧版本↓
更旧版本↓
更更旧版本
 

这个东西叫:

Undo Log 版本链


四、举一个特别容易理解的例子

最开始:

 
price = 1000
 

事务 10 修改:

 
price = 2000
 

那么可以理解成:

 
当前数据
price = 2000↓
Undo Log
price = 1000
 

后来事务 20 又修改:

 
price = 3000
 

于是:

 
当前数据
price = 3000↓
Undo Log
price = 2000↓
Undo Log
price = 1000
 

所以现在数据库实际上可以“找到”:

 
3000
2000
1000
 

不同事务可能看到不同版本。


五、那事务到底选择哪个版本?

这里就要引出一个非常重要的东西:

Read View(读视图)

你可以把 Read View 理解成:

“事务拍的一张时间照片。”

例如:

 
10:00:00事务A开始
 

此时数据库里面有:

 
事务1:已经提交
事务2:已经提交
事务3:正在执行
事务4:正在执行
 

事务 A 创建了一个 Read View。

它相当于说:

“我从 10:00:00 这个时间点来看数据库。”

那么之后即使别人修改了数据:

 
事务B → price 2000
事务C → price 3000
 

事务 A 根据自己的 Read View 判断:

“这些修改发生在我这个时间点之后,所以我不应该看到它们。”

于是它就通过 Undo Log 找到之前的版本。


六、所以 MVCC 可以记住这一句话

你面试的时候可以直接这样说:

MVCC 的核心就是:通过保存数据的多个历史版本,并结合 Read View 判断当前事务应该看到哪个版本,从而实现非阻塞的并发读取。


七、那为什么需要 MVCC?

因为如果没有 MVCC:

 
事务A:SELECT↓想读数据事务B:UPDATE↓正在修改数据
 

很容易产生:

 
读和写互相阻塞
 

而 MVCC 可以做到:

 
事务A:读取旧版本↓1000事务B:修改最新版本↓2000
 

两者可以同时进行。

所以:

MVCC 最大的价值之一,就是让读和写尽可能不互相阻塞。


八、但是有一个非常重要的误区

很多人学习 MVCC 会认为:

“MVCC 就是不用加锁。”

这是错的。

MVCC 主要解决的是:

普通的快照读(Snapshot Read)

例如:

 
SELECT * FROM user WHERE id = 1;
 

这种普通 SELECT 通常可以利用 MVCC。

但是:

 
SELECT * FROM user
WHERE id = 1
FOR UPDATE;
 

这是:

当前读(Current Read)

它需要读取最新版本,并且涉及锁。

所以你可以简单记:

 
普通 SELECT↓
快照读↓
MVCCSELECT ... FOR UPDATE↓
当前读↓
锁
 

九、MVCC 和隔离级别又有什么关系?

这也是面试非常喜欢问的。

以 InnoDB 为例:

READ COMMITTED

每次 SELECT 都可能产生新的 Read View。

所以:

 
第一次 SELECT↓
看到 1000别人修改↓
2000第二次 SELECT↓
可能看到 2000
 

这就是:

不可重复读


REPEATABLE READ

这是 MySQL InnoDB 默认的隔离级别。

事务第一次快照读之后,可以理解成:

 
我已经拍了一张照片
 

后面继续普通 SELECT

 
SELECT → 1000别人修改 → 2000SELECT → 1000
 

所以:

同一个事务中的多次快照读,通常看到的是一致的版本。


十、把整个 MVCC 串起来

你可以把它记成这张图:

 
                 MySQL MVCC│▼数据有多个版本│▼Undo Log│▼形成版本链│▼事务创建 Read View│▼判断哪个版本对当前事务可见│▼找到合适的历史版本│▼返回数据
 

真正理解到这里,你就已经理解 MVCC 的 80% 了。


十一、最后给你一个“人话版”总结

假设你和别人一起看一份 Excel。

原来:

 
余额 = 1000
 

别人把它改成:

 
余额 = 2000
 

你打开 Excel 的那一刻,系统给你:

拍了一张快照。

所以即使别人后来把:

 
1000 → 2000 → 3000
 

你在自己的事务里仍然可以根据这张“快照”看到:

 
1000
 

数据库为了做到这一点,就需要保存:

 
当前版本↓
历史版本↓
更早版本
 

这些历史版本主要通过 Undo Log 来实现,而判断“我到底应该看哪个版本”,靠的是 Read View

所以最核心的四个词:

MVCC = Undo Log + 版本链 + Read View + 可见性判断

如果你正在准备 Java/MySQL 面试,下一步最值得搞懂的是 “Read View 到底如何判断一个版本对事务可见”。这个一旦弄懂,MVCC、RR、RC、不可重复读、幻读就能全部串起来。