深入解析 MySQL MVCC:高并发背后的数据时光机
目录
前言
二、事务的隔离级别
三、MVCC的作用
1.提升并发性能
2.解决幻读问题(依赖于隔离级别)
3.非阻塞读
四、MVCC实现原理
1.必需的“隐藏字段”
2.核心组件:Undo Log
3.一致性视图:Read View
4.版本可见性规则
四、使用场景与局限性
使用场景
局限性
总结
前言
在现代数据库系统中,高并发处理能力是衡量其性能的关键指标。想象一下,当多个用户同时读写同一张表时,如何保证数据的一致性和隔离性,又能最大限度地提升并发性能?
MySQL的InnoDB存储引擎给出的核心答案之一就是MVCC。
一、MVCC是什么?
MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。
它是一种高级的数据库并发访问控制技术,其核心思想是:为每条记录保存多个历史版本。当不同事务同时访问数据库时,每个事务看到的的符合其隔离级别的、某个时间点的数据库快照,而不是直接操作同一份数据本身。
你可以把 MVCC 想象成一个 “数据时光机” 。每个事务在启动时,都获得了某个时刻的数据状态“快照”。无论之后其他事务如何修改或提交数据,这个快照在该事务的生命周期内都是不变的。这样,读操作和写操作就无需互相等待,极大地提升了并发效率。
二、事务的隔离级别
在MVCC中,事务的隔离级别决定了事务如何定义它们的ReadView。主要的隔离级别包括:
-
读未提交(READ UNCOMITTED):允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
-
读已提交(READ COMMITTED):允许读取已经提交的数据,可能会导致幻读和不可重复读。
-
可重复读(REPEATABLE READ):对同一字段的多次读取结果一致,除非数据是被本身事务自己所修改,可能会导致幻读。
-
可串行化(REPEATABLE READ):最高隔离级别,事务之间完全串行执行。
MySQL 的事务的隔离级别由高到低,分别为SERIALIZABLE、REPEATABLE、READ COMMITTED、 READREAD UNCOMITTED。低级别的隔离级别可以支持更高的并发处理,同时占用的系统资源更少。
三、MVCC的作用
MVCC 的出现,主要为了解决传统锁机制带来的性能瓶颈,它带来了以下几个核心优势:
1.提升并发性能
-
读写不阻塞:在 MVCC 下,普通的
SELECT
查询(快照读)不会被正在执行的UPDATE
、DELETE
等写操作阻塞。反之亦然(在大多数情况下)。这打破了读写互斥锁的限制,使得系统能够支持更高的并发连接数。
2.解决幻读问题(依赖于隔离级别)
-
在 可重复读(REPEATABLE READ) 隔离级别下,MVCC 通过一致性读视图(ReadView)保证了事务内多次读取同一范围的数据时,看到的结果是一致的,从而避免了“幻读”现象。
3.非阻塞读
-
这是 MVCC 最直接的好处。大部分读操作无需申请锁,几乎像是无锁操作,使得查询速度非常快,对 OLTP(联机事务处理)场景至关重要。
四、MVCC实现原理
MVCC 的实现并不是一个单一的功能,而是 InnoDB 通过一系列隐藏字段、Undo Log(回滚日志) 和 Read View(读视图) 协同工作的结果。
1.必需的“隐藏字段”
InnoDB 为每行记录(聚集索引)添加了两个重要的隐藏字段:
-
DB_TRX_ID
(6字节):事务ID。表示最近一次插入或更新该行记录的事务ID。DELETE
操作在 InnoDB 内部也被视为一次更新。 -
DB_ROLL_PTR
(7字节):回滚指针。指向该行记录上一个历史版本的指针,这个历史版本存储在 Undo Log 中。
(此外,还有一个隐藏的 DB_ROW_ID
字段作为行ID,但与 MVCC 关系不大。)
2.核心组件:Undo Log
Undo Log 主要用来保证事务的原子性和回滚,同时也是 MVCC 实现多版本的关键。
-
当一个事务更新某行数据时,会先将该行的原始数据复制一份到 Undo Log 中。
-
然后用新的数据更新当前行,并更新
DB_TRX_ID
为当前事务ID,DB_ROLL_PTR
指向刚刚写入 Undo Log 的旧版本记录。 -
旧版本记录的回滚指针又会指向更早的版本。这样,一行数据的所有历史版本就被
DB_ROLL_PTR
指针串成了一个版本链。
3.一致性视图:Read View
Read View 是事务在执行快照读时产生的读视图。它决定了当前事务能看到哪个版本的数据。
Read View 主要包含以下关键信息:
-
m_ids
:生成 Read View 时,系统中活跃的(未提交的)事务ID列表。 -
min_trx_id
:m_ids
中最小的事务ID。 -
max_trx_id
:生成 Read View 时,系统尚未分配的下一个事务ID(即当前最大事务ID+1)。 -
creator_trx_id
:创建该 Read View 的事务ID。
4.版本可见性规则
当一条记录有多个版本时,事务如何判断应该看到哪个版本?规则如下:
沿着版本链,对比每个版本的 DB_TRX_ID
和当前 Read View 中的属性:
-
如果
DB_TRX_ID
<min_trx_id
,说明该版本在 Read View 创建前已提交,可见。 -
如果
DB_TRX_ID
>=max_trx_id
,说明该版本在 Read View 创建后才开启,不可见。 -
如果
min_trx_id
<=DB_TRX_ID
<max_trx_id
,则需要进一步判断:-
如果
DB_TRX_ID
在m_ids
(活跃事务列表)中,说明创建该版本的事务还未提交,不可见。 -
如果
DB_TRX_ID
不在m_ids
中,说明创建该版本的事务已提交,可见。
-
如果某个版本对当前事务不可见,就顺着回滚指针 DB_ROLL_PTR
找到上一个版本,重复上述判断规则,直到找到可见的版本为止。
RR隔离示例:
注意:对于 读已提交(READ COMMITTED) 和 可重复读(REPEATABLE READ) 隔离级别,生成 Read View 的时机不同:
-
RC:每次执行快照读时都会生成一个新的 Read View。因此它能读到其他事务已提交的最新数据。
-
RR:只在第一次执行快照读时生成一个 Read View,后续所有读操作都复用这个视图。因此它看到的数据始终和第一次读时一致。
四、使用场景与局限性
使用场景
-
高并发读多写少的OLTP系统:如电商网站、论坛、SaaS应用等,读请求远多于写请求,MVCC 的非阻塞读特性优势巨大。
-
需要高一致性读的报告与分析:一个长时间运行的报表查询,即使背后数据在不断变化,它也能获得一个一致性的快照,保证数据准确性。
-
实现非锁定一致性备份:像
mysqldump --single-transaction
这样的工具,就是通过开启一个事务并利用 MVCC 来获取一个一致性的数据库快照进行备份,而不需要锁表。
局限性
-
额外的存储空间:MVCC 需要维护多个数据版本,Undo Log 会占用大量磁盘空间。
-
额外的维护成本:需要定期清理不再需要的旧版本数据(Purge 操作),否则会导致 Undo Log 膨胀。
-
无法完全摆脱锁:MVCC 通常与“next-key lock”配合使用来解决幻读。对于诸如
SELECT ... FOR UPDATE
、UPDATE
、DELETE
等当前读操作,仍然需要加锁来确保数据安全
总结
MVCC 是 MySQL InnoDB 实现高性能并发控制的基石。它通过巧妙的“数据多版本”和“一致性读视图”机制,完美地平衡了并发性能与数据一致性之间的矛盾。理解 MVCC,不仅有助于我们更好地设计数据库表结构和编写 SQL,也能在遇到并发问题时,快速定位其根源。
下次当你享受 MySQL 的高并发查询性能时,别忘了背后这位默默工作的“数据时光机”——MVCC。