在探讨数据库并发控制时,我们常会接触到“快照读”、“读提交(RC)”、“可重复读(RR)”这几个核心概念。理解它们在 MySQL InnoDB引擎下的行为差异,对于设计高并发应用至关重要。
核心概念解析
快照读 (Snapshot Read)
MySQL 数据库,InnoDB存储引擎,为了提高并发,使用了MVCC(多版本并发控制)机制。在并发事务环境下,通过读取数据行的历史数据版本,来实现一种不加锁的一致性读(Consistent Nonlocking Read),这就是快照读。
读提交 (Read Committed, RC)
- 这是数据库事务隔离级别的一种。
- 它解决了“读脏”(Dirty Read)问题,保证事务读取到的数据行都是其他已提交事务写入的。
- 但它可能存在“不可重复读”或“幻读”问题,即同一个事务内,连续执行相同的查询可能读到不同的结果集。
可重复读 (Repeated Read, RR)
- 同样是事务隔离级别的一种。
- 它不仅解决了“读脏”问题,还致力于解决“不可重复读”问题,保证在同一个事务里,连续相同的查询总能读到相同的结果集。
那么,在读提交(RC)和可重复读(RR)这两种不同的事务隔离级别下,快照读的行为究竟有什么不同呢?
核心结论
我们先明确结论:
- 事务总是能够读取到自己写入(通过
UPDATE、INSERT、DELETE 操作)的行记录。
- 在 RC 级别下,快照读总是能读取到最新的、已提交的行数据快照。
- 在 RR 级别下,事务首次执行
SELECT 操作(快照读)的时间点记为 T。此后,在该事务内进行的任何快照读,都不会读取到时间点 T 之后才提交的事务所写入的记录,以此保证“可重复读”。
关键点:快照读的可见性与事务的开始时间无直接关系,而是与事务内首次读操作的时间点有关。同时,由于是不加锁读,其行为与锁的互斥关系也不大。
实例分析
我们通过一个InnoDB表 t(id PK, name) 来具体说明,表中初始有三条记录:
1, shenjian
2, zhangsan
3, lisi
案例 1
两个并发事务A和B按以下时间序执行(A先开始,B先结束):
A1: start transaction;
B1: start transaction;
A2: select * from t;
B2: insert into t values (4, wangwu);
A3: select * from t;
B3: commit;
A4: select * from t;
提问1:在RR隔离级别下,A2、A3、A4分别读到什么结果集?
回答:
- A2是事务A的首次读,时间点为T,结果集为
{1, 2, 3}。
- A3发生在B提交前,读不到B未提交的数据,结果集仍为
{1, 2, 3}。
- A4发生在B提交后,但在RR下,事务A的读视图基于时间点T建立,T之后提交的事务B的修改不可见,因此结果集依然是
{1, 2, 3}。
提问2:在RC隔离级别下,A2、A3、A4的结果集是什么?
回答:
- A2:
{1, 2, 3}
- A3:B未提交,所以仍是
{1, 2, 3}
- A4:B已提交,RC下每次读都获取最新已提交快照,因此结果集为
{1, 2, 3, 4}。
案例 2
调整事务开始顺序(B先开始,B先结束):
B1: start transaction;
A1: start transaction;
A2: select * from t;
B2: insert into t values (4, wangwu);
A3: select * from t;
B3: commit;
A4: select * from t;
提问3 & 4:RR和RC下的结果分别是?
回答:事务的开始时间不同并不影响快照读的判定逻辑。因此,结果集与案例1完全相同。
案例 3
事务A和B(A先开始,B先结束),但A的读操作在B提交之后:
A1: start transaction;
B1: start transaction;
B2: insert into t values (4, wangwu);
B3: commit;
A2: select * from t;
提问5:在RR下,A2的结果集?
回答:A2是事务A的首次读,时间点为T。此时事务B已在时间T之前提交,所以A2能读取到B已提交的修改,结果为 {1, 2, 3, 4}。
提问6:在RC下,A2的结果集?
回答:RC下读取最新已提交数据,结果同样是 {1, 2, 3, 4}。
案例 4
调整案例3的开始顺序(B先开始,B先结束):
B1: start transaction;
A1: start transaction;
B2: insert into t values (4, wangwu);
B3: commit;
A2: select * from t;
提问7 & 8:RR和RC下的结果?
回答:同理,事务开始顺序不影响,结果集与案例3相同。
总结与原理
通过以上案例,我们可以清晰地看到差异并理解其背后的机制:
- 在 RR(可重复读) 隔离级别下,事务在第一次执行快照读操作时,会创建一个Read View(读视图)。这个视图决定了该事务后续所有快照读能看到的数据版本。此后,即使其他事务提交了新的数据,只要该修改是在本事务的Read View创建之后提交的,就对当前事务不可见,从而保证了“可重复读”。
- 在 RC(读提交) 隔离级别下,事务在每次执行快照读操作时,都会创建一个新的Read View。因此,每次读都能看到所有在该读操作之前已经提交的事务所做的修改,实现了“读提交”的语义。
理解“Read View”的创建时机,是掌握RC与RR下快照读差异的关键。这比单纯记住结论更重要,它能帮助你在复杂的并发场景中,准确推断数据可见性。