mvcc 只支持 RC 和 RR 两种事务隔离级别。
1:READ COMMITTED,读已提交
每次读取数据前都生成一个ReadView
比方说现在系统里有两个 id 分别为 100 、 200 的事务在执行
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
注:事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。
此刻,表 t 中 id 为 1 的记录得到的版本链表如下所示:
假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值为'刘备'
这个 SELECT1 的执行过程如下:
在执行 SELECT 语句时会先生成一个 ReadView , ReadView 的 m_ids 列表的内容就是 [100,
200] 。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 c 的内容是 '张飞' ,该版本的trx_id 值为 100 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列 c 的内容是 '关羽' ,该版本的 trx_id 值也为 100 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列 c 的内容是 '刘备' ,该版本的 trx_id 值为 80 ,小于 m_ids 列表中最小的事务id 100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 c 为 '刘备' 的记录。
之后,我们把事务id为 100 的事务提交一下,就像这样:
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
COMMIT;
然后再到事务id为 200 的事务中更新一下表 t 中 id 为1的记录:
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...
UPDATE t SET c = '赵云' WHERE id = 1;
UPDATE t SET c = '诸葛亮' WHERE id = 1;
此刻,表 t 中 id 为 1 的记录的版本链就长这样:
然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个id为 1 的记录,如下:
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200均未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值为'刘备'
# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值为'张飞'
这个 SELECT2 的执行过程如下:
在执行 SELECT 语句时会先生成一个 ReadView , ReadView 的 m_ids 列表的内容就是 [200] (事务id为 100 的那个事务已经提交了,所以生成快照时就没有它了)。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 c 的内容是 '诸葛亮' ,该版本的 trx_id 值为 200 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列 c 的内容是 '赵云' ,该版本的 trx_id 值为 200 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列 c 的内容是 '张飞' ,该版本的 trx_id 值为 100 ,比 m_ids 列表中最小的事务
id 200 还要小,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 c 为 '张飞' 的记录。
以此类推,如果之后事务id为 200 的记录也提交了,再此在使用 READ COMMITTED 隔离级别的事务中查询表 t 中 id 值为 1 的记录时,得到的结果就是 '诸葛亮' 了。
使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
2:REPEATABLE READ,可重复读
在事务开始后第一次读取数据时生成一个ReadView
对于使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。
比方说现在系统里有两个 id 分别为 100 、 200 的事务在执行:
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
此刻,表 t 中 id 为 1 的记录得到的版本链表如下所示:
假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行:
# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值为'刘备'
这个 SELECT1 的执行过程如下:
在执行 SELECT 语句时会先生成一个 ReadView , ReadView 的 m_ids 列表的内容就是 [100,
200] 。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 c 的内容是 '张飞' ,该版本的trx_id 值为 100 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列 c 的内容是 '关羽' ,该版本的 trx_id 值也为 100 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列 c 的内容是 '刘备' ,该版本的 trx_id 值为 80 ,小于 m_ids 列表中最小的事务
id 100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 c 为 '刘备' 的记录。
之后,我们把事务id为 100 的事务提交一下,就像这样:
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
COMMIT;
然后再到事务id为 200 的事务中更新一下表 t 中 id 为1的记录:
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...
UPDATE t SET c = '赵云' WHERE id = 1;
UPDATE t SET c = '诸葛亮' WHERE id = 1;
此刻,表 t 中 id 为 1 的记录的版本链就长这样:
然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个id为 1 的记录,如下:
# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200均未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值为'刘备'
# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM t WHERE id = 1; # 得到的列c的值仍为'刘备'
这个 SELECT2 的执行过程如下:
因为之前已经生成过 ReadView 了,所以此时直接复用之前的 ReadView ,之前的 ReadView 中的m_ids 列表就是 [100, 200] 。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 c 的内容是 '诸葛亮' ,该版本的 trx_id 值为 200 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列 c 的内容是 '赵云' ,该版本的 trx_id 值为 200 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
下一个版本的列 c 的内容是 '张飞' ,该版本的 trx_id 值为 100 ,而 m_ids 列表中是包含值为
100 的事务id的,所以该版本也不符合要求,同理下一个列 c 的内容是 '关羽' 的版本也不符合要求。继续跳到下一个版本。
下一个版本的列 c 的内容是 '刘备' ,该版本的 trx_id 值为 80 , 80 小于 m_ids 列表中最小的事务id 100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 c 为 '刘备' 的记录。
也就是说两次 SELECT 查询得到的结果是重复的,记录的列 c 值都是 '刘备' ,这就是 可重复读 的含义。
如果我们之后再把事务id为 200 的记录提交了,之后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个id为 1 的记录,得到的结果还是 '刘备' ,具体执行过程大家可以自己分析一下。
3:小结
从上边的描述中我们可以看出来,所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用 READ COMMITTD 、 REPEATABLE READ 这两种隔离级别的事务在执行普通的SEELCT 操作时访问记录的版本链的过程,这样子可以使不同事务的 读-写 、 写-读 操作并发执行,从而提升系统性能。
READ COMMITTD 、 REPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView 的时机不同, READ COMMITTD 在每一次进行普通 SELECT 操作前都会生成一个 ReadView ,而 REPEATABLEREAD 只在第一次进行普通 SELECT 操作前生成一个 ReadView ,之后的查询操作都重复这个 ReadView就好了。
如果您觉得文章好看,欢迎点赞收藏加关注,一连三击呀,您的肯定是我持续输出的动力,感谢!!☺☻