阿国运维网技术分享平台:桌面运维、网络运维、系统运维、服务器运维(及云服务器),精品软件分享、阿国网络、尽在北京运维网
背景介绍
许多人认为 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种乐观锁的实现方式,我们先来了解一下什么是乐观锁和悲观锁。
数据库并发控制——锁机制
在数据库系统中,并发控制是保证多个事务在并发执行时数据一致性的核心技术。传统的并发控制方法是使用 锁,它是一种直接而有效的解决方案。
锁的分类: DQL(Data Query Language,数据查询语言):查询数据(如 SELECT)时使用 读锁。DML(Data Manipulation Language,数据操作语言):对数据进行增、删、改操作(如 INSERT、DELETE、UPDATE)时使用 写锁。DDL(Data Definition Language,数据库定义语言):定义和修改表结构(如 CREATE TABLE、DROP TABLE)时,通常会使用 元数据锁。
悲观锁和乐观锁
悲观锁
概念: 悲观锁假定会发生并发冲突,因此在对资源进行操作之前,会先加锁,确保其他事务无法同时访问该资源。 特点: 需要加锁,锁定资源后,其他线程对该资源的操作会被阻塞。 开销较大,可能导致性能下降。 应用场景: 适用于并发冲突频繁的场景。 例子: 数据库中的 行级锁 或 Java 中的 synchronized关键字。
乐观锁
概念: 乐观锁假定不会发生并发冲突,因此不加锁,而是在更新数据时,通过比较版本号或条件检查来保证操作的正确性。 特点: 不加锁,操作更轻量级。 需要在操作完成后检查是否发生冲突。 应用场景: 适用于并发冲突较少的场景。 实现方式: 版本号机制:每次修改数据时,更新版本号。更新操作成功的前提是版本号没有变化。 CAS(Compare And Swap):通过原子性比较和更新操作实现乐观锁。
数据库并发控制——MVCC 的引入
许多人认为 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种乐观锁的实现方式,但 MVCC 的核心在于通过 版本控制和可见性算法 来实现数据库的并发控制。InnoDB 的 MVCC 通过隐藏字段、Undo Log 和 Read View 协同工作,实现了高效的多版本并发控制:
隐藏字段:
提供版本控制信息,判断事务的可见性。 Undo Log:
保存数据的历史版本,支持事务回滚和快照读。 Read View:
在事务启动时生成的元数据,用于确定哪些数据版本对事务可见。
MVCC 和锁机制的对比
| 特性 | 锁(悲观锁/乐观锁) | MVCC |
|---|---|---|
| 加锁开销 | ||
| 并发性能 | ||
| 实现方式 | ||
| 适用场景 | ||
| 优缺点 |
MySQL 的多版本并发控制 (MVCC)
多版本并发控制 (MVCC, Multi-Version Concurrency Control) 是 MySQL 用于实现事务隔离的一种机制,主要应用于 InnoDB 存储引擎。通过 MVCC,MySQL 可以在高并发环境下实现 读写并行,同时减少锁的使用,提高性能。
快照读和当前读
快照读(Snapshot Read):
读取的是数据的快照版本(历史版本),即事务开始时的数据状态,而不是最新的。 常见的 SELECT语句都是快照读,除非显式使用加锁查询。当前读(Current Read):
读取的是最新版本的数据,并且会对读取的数据加锁以确保一致性。 常见的当前读操作包括:
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETEINSERT
快照读和当前读的对比
| 操作类型 | 快照读(Snapshot Read) | 当前读(Current Read) |
|---|---|---|
| 使用场景 | SELECT 查询 | SELECT ... FOR UPDATE等) |
| 是否加锁 | ||
| 数据版本 | ||
| 是否支持 MVCC |
隐藏的系统列
InnoDB 存储引擎会为每一行记录添加了以下 三个隐藏的系统列,用于实现 MVCC:
| 是否删除 | 事务 ID | 回滚指针 |
| isDelete | |
| DB_TRX_ID | |
| DB_ROLL_PTR | undo log 日志,保存行的历史版本。 |
Undo Log(回滚日志)
Undo Log 是 InnoDB 存储引擎用于实现事务回滚、快照读(MVCC)的重要机制之一。它记录数据修改前的旧版本,并通过回滚指针(DB_ROLL_PTR)形成一条 Undo 日志链。
功能:
事务回滚:当事务未提交或被回滚时,Undo Log 提供修改前的数据,用于恢复到原始状态,撤销未提交事务对数据库的影响。 多版本控制(MVCC):Undo Log 保存了数据的历史版本,通过回滚指针( DB_ROLL_PTR),其他事务可以通过 Undo Log 获取旧版本数据。
Undo Log 的特性:
逻辑日志:记录逻辑上的操作。例如:
删除一条记录时,Undo Log 会记录一个对应的“插入操作”。 更新一条记录时,Undo Log 会记录一个对应的“反向更新操作”。 存储位置:
Undo Log 存储在 回滚段 中。 Undo Log 的分类:
Insert Undo Log: 记录事务插入数据时的日志。 特点:事务提交后即可丢弃,因为没有其他事务需要访问它。 Update Undo Log: 记录事务更新或删除数据时的日志。 特点:事务提交后,仍需保留以支持快照读,只有当没有比该日志更早的 Read View存在时,才能删除。问题与优化:
长事务可能导致 Undo Log 无法及时清理,因为较早的 Read View仍然需要访问旧版本数据。这会导致存储空间占用过大,建议避免长时间未提交的事务。
Read View(读视图)
Read View 是事务在执行快照读(Snapshot Read)时生成的一种快照机制,用于判断当前事务对哪些数据版本可见。
功能:
Read View 确保事务在快照读时能够看到一致性的数据。 通过可见性算法(Visibility Algorithm)判断某个数据版本是否对当前事务可见。 Read View 的组成:
alive_trx_list:当前系统中活跃的事务 ID 列表,包含所有未提交事务的 ID。 up_limit_id:alive_trx_list中的最小事务 ID。 low_limit_id:系统当前分配的最大事务 ID 加 1。
可见性算法(Visibility Algorithm)
在生成 Read View 后,InnoDB 通过以下步骤判断数据版本(DB_TRX_ID)是否对当前事务可见, MVCC 的可重复读(Repeatable Read)隔离级别判断如下:
判断是否早于活跃事务的最小 ID:
如果 DB_TRX_ID < up_limit_id,表明该版本在生成 Read View 前已提交,对当前事务可见。判断是否晚于最新事务的最大 ID:
如果 DB_TRX_ID >= low_limit_id,表明该版本在生成 Read View 后才生成,对当前事务不可见。判断是否属于活跃事务:
如果 DB_TRX_ID在alive_trx_list中,说明生成 Read View 时该事务仍未提交,因此该版本对当前事务不可见。通过回滚指针查找可见版本:
如果数据版本不可见且 ROLL_PTR不为空,则通过ROLL_PTR指向的 Undo Log 查找更早的版本,重复上述判断,直到找到可见的版本。
MVCC 支持的事务隔离级别
1. 读未提交(Read Uncommitted)
Read View:
不使用 Read View。 读取数据时直接读取最新版本,无论数据是否由其他事务提交。 可见性规则:
所有事务的最新修改版本对当前事务可见。 即使其他事务未提交的数据,也可以被读取(会发生脏读)。 特点:
无需 Undo Log,也不使用 alive_trx_list等 Read View 属性。
2. 读已提交(Read Committed)
Read View:
每次查询都会生成新的 Read View,因此每次查询的结果可能不同。 Read View 只在当前查询的上下文中生效,不跨查询复用。 可见性规则:
如果 DB_TRX_ID小于up_limit_id(即数据版本在当前查询的 Read View 生成之前已提交),则该版本对当前事务可见。如果 DB_TRX_ID在活跃事务列表中,说明该版本由未提交事务生成,对当前事务不可见。特点:
数据版本的可见性随着每次查询变化。 防止脏读,但可能发生不可重复读。
3. 可重复读(Repeatable Read)
Read View:
事务开始时生成一次 Read View,整个事务期间使用同一个快照,确保读取结果一致。 该 Read View 的属性( up_limit_id、low_limit_id和alive_trx_list)在事务期间不会变化。可见性规则:
如果 DB_TRX_ID < up_limit_id,说明数据版本在事务开始前已提交,对当前事务可见。如果 DB_TRX_ID >= low_limit_id,说明数据版本在事务开始后生成,对当前事务不可见。如果 DB_TRX_ID在alive_trx_list中,说明数据版本由未提交的事务生成,对当前事务不可见。特点:
读操作始终基于事务开始时生成的 Read View。 防止脏读和不可重复读,但可能发生幻读。
4. 串行化(Serializable)
Read View:
不使用 Read View。 通过加锁(如共享锁、排他锁)实现事务隔离。 可见性规则:
每次读取时都会加锁,确保当前读操作的可见性。 因为加锁阻塞了其他事务的修改或读取,因此不存在不可见的问题。 特点:
防止脏读、不可重复读和幻读。 并发性能较低,但数据一致性最高。
整体工作流程
事务修改数据时:
写入 Undo Log,保存旧版本数据。 更新 DB_TRX_ID和DB_ROLL_PTR。事务执行快照读时:
若不可见,使用 DB_ROLL_PTR查找历史版本。生成 Read View,记录当前系统中活跃事务列表。 判断数据版本是否可见: 事务提交时:
Insert Undo Log 可直接删除。 Update Undo Log 保留,用于支持其他事务的快照读。 事务回滚时:
通过 Undo Log 恢复旧版本数据,撤销事务的影响。
总结
MVCC 是 InnoDB 存储引擎中实现事务隔离和提高并发性能的关键机制。
通过维护多个数据版本和 Undo Log,实现快照读和当前读,避免了大量加锁操作。
Undo Log:
是 MVCC 的基础,记录旧版本数据,支持事务回滚和快照读。 分类为 Insert Undo Log 和 Update Undo Log。 Read View:
确保快照读的隔离性,通过可见性算法判断数据版本是否可见。 隔离级别的不同会影响 Read View 的生成时机。 两者配合:
Undo Log 提供数据的历史版本,Read View 判断哪些版本对当前事务可见,共同实现事务的并发控制和一致性。
MVCC 的优点
提高并发性能:
快照读不需要加锁,避免了读写之间的冲突。 减少锁开销:
大量读操作可以通过读取历史版本完成,无需加锁,提高效率。 支持事务隔离:
MVCC 能够在不同的隔离级别下提供一致的数据读取。
MVCC 的局限性
占用存储空间:
Undo Log 的存在会增加存储开销,特别是长事务会导致 Undo Log 增长。 长事务的性能问题:
长事务可能会导致 Undo Log 不能被及时清理,增加性能开销。 仅适用于读写混合场景:
如果事务中大量是写操作,MVCC 的优势会减弱,因为写操作仍需加锁。
示例:MVCC 的快照读
1. 表结构
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
status VARCHAR(20)
);
2. 插入数据
INSERT INTO orders (status) VALUES ('pending'), ('shipped'), ('delivered');
3. 启动事务并模拟并发查询 事务 A:
START TRANSACTION;
SELECT * FROM orders; -- 快照读,读取事务开始时的版本
UPDATE orders SET status = 'cancelled' WHERE id = 1; -- 当前读,修改最新版本
事务 B:
START TRANSACTION;
SELECT * FROM orders WHERE id = 1; -- 读取事务 A 修改前的快照版本
结果:
事务 A 在事务 B 提交之前,可以看到修改后的状态。 事务 B 在事务 A 提交之前,读取的是修改前的状态。





