REPEATABLE READ如何确定数据行的版本?
众所周知,在事务隔离级别REPEATABLE READ
的情况下,我们读到的行数据版本是事务开始时候的版本,但是在具体实现上,这里有一个疑问,数据库到底使用的是begin
开始时候的版本?还是begin
开始后,我们第一次读相关行数据时候的版本呢?也就是什么时候决定本次事务能够读到的行的版本。先不要急着下结论,让我们以mysqld 8.0.20-debug
为例子,看下MySQL
究竟是怎么实现的。
1 表
表的schema为:
create table t(
a int PRIMARY KEY,
b int
);
insert into t values(1,1),(2,2),(4,4),(5,5);
2 第一个例子
两个session的操作顺序如下:
时间线 | session A | session B |
---|---|---|
1 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; begin; |
|
2 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; begin; update t set b = b * 10 where a = 2; commit; |
|
3 | select * from t where a = 2; |
在sessionA中,读到的结果如下所示:
+---+------+
| a | b |
+---+------+
| 2 | 20 |
+---+------+
可以看到,似乎MySQL是在第一次读该行时决定本次事务应该使用哪个版本?但到底是不是这样呢?让我们把数据恢复到一开始的值,看第二个例子。
3 第二个例子
两个session的操作顺序如下:
时间线 | session A | session B |
---|---|---|
1 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; begin; select * from t where a = 4;//这里读取一个不相关的行 |
|
2 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; begin; update t set b = b * 10 where a = 2; commit; |
|
3 | select * from t where a = 2; |
在这个例子里面,不同的地方在表格中已经加黑,而在这个例子里面,sessionA中,读到的结果如下所示:
+---+------+
| a | b |
+---+------+
| 2 | 2 |
+---+------+
而这一次,因为我们读到了一个不相关的值,却导致了a=2
这一行读到的值发生了变化,那么结果其实很显而易见了。
MySQL在决定REPEATABLE READ使用的行版本的时候,其实是在用户第一次读相关行的时候决定的,只不过,这个是以page为单位的,这也就是第二个例子里面,为啥读了a=4的值却导致了a=2的值发生了变化的原因。
这是一个小小的区别,可以帮助我们更好的理解MySQL的运行机制。