理解 MySQL 中的 MVCC 和快照读
在 MySQL 中,MVCC(多版本并发控制) 是用于处理高并发事务的关键机制。它通过管理数据的多个版本,允许多个事务并行运行,确保数据库的一致性和隔离性,避免加锁,提高并发能力。
什么是 MVCC
MVCC(多版本并发控制)是一个允许多个事务并行执行而不会互相干扰的机制。它通过给每个事务分配一个唯一的事务 ID,并维护数据的多个版本,确保并发事务之间的数据一致性,避免加锁,提高并发能力。
MVCC 的核心元素
- 事务 ID:每个事务都有一个唯一的事务 ID。通过事务 ID,InnoDB 能够区分不同版本的数据。
- 回滚段(Undo Logs):每个操作(如插入、更新、删除)都会在回滚段中记录一份旧的数据版本。当需要恢复到某个时间点时,InnoDB 会使用这些回滚日志。
- 可见性:事务在读取数据时,会根据事务 ID 和回滚段来判断数据的版本,确保查询的数据是事务开始时的快照。
快照读(Consistent Read)实现
快照读是 MVCC 的核心,指的是在查询时提供一致的视图,即使其他事务在此期间进行了更新。具体实现如下:
- 事务开始时创建一个快照:InnoDB 会为每个事务创建一个数据快照,保存该时刻的数据版本。
- 查询时使用快照数据:当事务执行查询时,InnoDB 会返回符合事务开始时快照的数据,而不是最新的数据。
- 通过 Undo Logs 实现一致性:InnoDB 会通过扫描回滚段(Undo Logs)来获取历史版本的数据,确保快照数据的一致性。
快照读的工作原理
- 在查询时,InnoDB 会根据事务 ID 和回滚段中的数据版本,确定哪些数据是可见的。
- 事务会看到从其开始时的快照数据,而其他事务对数据的插入、更新操作不会影响当前事务的查询结果。
快照读示例:
- 创建示例表
首先,我们创建一个 users 表,并插入一些初始数据:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
balance DECIMAL(10,2)
) ENGINE=InnoDB;
INSERT INTO users (name, balance) VALUES ('Alice', 100.00), ('Bob', 200.00);
- 开启两个事务,模拟 MVCC 读取数据
事务 A(读取 Alice 的余额)
BEGIN;
SELECT * FROM users WHERE name = 'Alice'; -- 快照读
此时,Alice 的 balance 是 100.00(读取的是旧版本快照)。
事务 B(更新 Alice 的余额)
BEGIN;
UPDATE users SET balance = 150.00 WHERE name = 'Alice';
COMMIT;
此时,Alice 的 balance 更新为 150.00,但 事务 A 仍然看到 100.00,因为它使用的是 事务启动时的快照。
事务 A 再次读取 Alice 的余额
SELECT * FROM users WHERE name = 'Alice'; -- 仍然是快照读
返回结果仍然是 100.00,说明 事务 A 看到的仍是旧版本数据(MVCC 机制生效)。
事务 A 提交后再查询
COMMIT;
SELECT * FROM users WHERE name = 'Alice'; -- 现在读到的是最新数据
此时,Alice 的 balance 才变成 150.00。
当前读
接上文的例子,如果需要在事务中读取实时数据,而不是快照读,可在select语句后加上行锁for update
或者共享锁lock in share mode
。
BEGIN;
SELECT * FROM users WHERE name = 'Alice' lock in share mode; -- 当前读
SELECT * FROM users WHERE name = 'Alice' for update; -- 当前读
此时,Alice 的 balance 才变成 150.00。
tips:由于是当前读,则MySQL会自动帮我们加上间隙锁。
间隙锁
间隙锁是为了解决的在可重复读事务模式下的幻读问题。在使用当前读(如 SELECT FOR UPDATE
或 SELECT LOCK IN SHARE MODE
)时,InnoDB 会加锁以确保数据一致性,并可能触发 间隙锁。间隙锁用于防止其他事务在当前事务所锁定的数据范围内插入数据,从而避免幻读现象。
- 当前读触发间隙锁
当使用SELECT FOR UPDATE
或SELECT LOCK IN SHARE MODE
时,InnoDB 会锁定符合条件的记录,并锁定相关的 间隙,防止其他事务插入符合查询条件的数据。
BEGIN;
SELECT * FROM users WHERE age > 20 FOR UPDATE;
此时其他事务无法再插入age
大于20的记录,会阻塞等待,直到上述事务提交
2. 一致性读不触发间隙锁
普通的 SELECT 查询不会触发间隙锁,因为它依赖 MVCC 来提供数据一致性,而不会加锁或防止其他事务插入数据。
SELECT * FROM users WHERE age > 20;
tips:间隙锁(Gap Lock)是 左闭右开 的,即它锁定的范围包括区间的左边界,但不包括右边界。左闭右开([a, b))意味着间隙锁会锁住一个区间的左边界(a),但不锁住右边界(b)。例如,如果有一个条件是 age BETWEEN 20 AND 30
,间隙锁会锁定从 age = 20
到 age = 30
之间的区间,但不会包括 age = 30 的数据。
评论区