外观
怎么实现可重复读?MVCC 怎么保证可重复读?
可重复读的概念:
可重复读(Repeatable Read
)是数据库事务的一种隔离级别。在该隔离级别下,一个事务在执行过程中多次读取同一数据时,会保证所读取到的数据始终保持一致,即不会受到其他事务对该数据进行的修改操作的影响。
数据库实现可重复读的一般方式:
在数据库中,实现可重复读通常有两种主要方式:锁机制和多版本并发控制(MVCC
)。
锁机制:
通过对读取的数据加锁,防止其他事务对这些数据进行修改。例如,在 MySQL
的 InnoDB
存储引擎中,如果使用 SELECT ... FOR UPDATE
语句,会对读取的行加排他锁,其他事务无法对这些行进行修改,直到当前事务结束释放锁。但这种方式会降低并发性能,因为其他事务需要等待锁的释放。
MVCC
实现可重复读:
MVCC
通过为数据创建多个版本,让不同的事务可以访问不同版本的数据,从而实现并发控制。在 MySQL
的 InnoDB
存储引擎中,MVCC
通过以下方式实现可重复读:
1.隐藏列和事务 ID
隐藏列:
InnoDB
为表中的每一行记录添加了三个隐藏列,分别是DB_TRX_ID
(记录最后一次插入或更新该行的事务 ID)、DB_ROLL_PTR
(回滚指针,指向该行记录的上一个版本)和DB_ROW_ID
(行ID
,当表中没有主键或唯一非空索引时,InnoDB
会自动生成)。事务
ID
:每个事务在启动时会被分配一个唯一的事务ID
,事务ID
是递增的。
2.一致性视图(Read View
)
创建时机:在可重复读隔离级别下,事务在首次进行快照读(普通的
SELECT
查询)时会创建一个一致性视图。这个视图包含了当前所有活跃事务的ID
列表,以及一个低水位和高水位。低水位和高水位:低水位是指创建视图时系统中已经分配的最小事务
ID
,高水位是指下一个将被分配的事务ID
。判断数据可见性
- 如果数据的
DB_TRX_ID
小于低水位,说明该数据是在当前事务启动之前就已经提交的事务修改的,数据对当前事务可见。 - 如果数据的
DB_TRX_ID
大于等于高水位,说明该数据是在当前事务启动之后才开始的事务修改的,数据对当前事务不可见。 - 如果数据的
DB_TRX_ID
在低水位和高水位之间,需要检查该事务ID
是否在活跃事务列表中。如果在,说明该事务还未提交,数据对当前事务不可见;如果不在,说明该事务已经提交,数据对当前事务可见。
- 如果数据的
3.快照读:在可重复读隔离级别下,同一个事务内的多次快照读操作都会使用同一个一致性视图。因此,无论其他事务对数据进行了怎样的修改,当前事务在多次读取同一数据时,看到的都是创建视图时的数据版本,从而保证了可重复读。
示例: 假设有一个 users
表,包含 id
和 name
两列,初始数据如下:
id | name |
---|---|
1 | Alice |
现在有两个事务 T1
和 T2
并发执行:
- 事务
T1
:
START TRANSACTION;
-- 第一次快照读,创建一致性视图
SELECT * FROM users WHERE id = 1; -- 返回 (1, Alice)
- 事务
T2
:
START TRANSACTION;
UPDATE users SET name = 'Bob' WHERE id = 1;
COMMIT;
- 事务
T1
:
-- 第二次快照读,使用之前创建的一致性视图
SELECT * FROM users WHERE id = 1; -- 仍然返回 (1, Alice)
COMMIT;
在上述示例中,事务 T1
在首次进行快照读时创建了一致性视图,之后即使事务 T2
修改并提交了数据,事务 T1
的第二次快照读仍然使用同一个视图,因此读取到的数据与第一次相同,保证了可重复读。