参考:

【MySQL(5)| 五分钟搞清楚 MVCC 机制】

前言

  1. Multiversion concurrency control 多版本并发控制
  2. 当部分数据在被某个事务处理的同时,有其他事务对这些数据进行访问,则会使用多版本并发控制来避免
  3. 由于写操作的堵塞,引发的读操作失败的并发问题。

mysql 默认的隔离级别为RR(Repeatable Read),理论上(具体可以查看我的前一篇文章ACID),在此隔离级别下,不可重复读是不会发生的。

当我们分别启动两个事务

事务一执行以下命令:

  1. UPDATE test_zq SET test_id = 20 WHERE id = 1;

事务二执行以下命令:

  1. SELECT * FROM test_zq WHERE id = 1;

image.png
(图片出自【MySQL(5)| 五分钟搞清楚 MVCC 机制】
根据事务隔离级别来看,事务二理论上获得 X 锁,此时事务一是无法再获取到 S 锁去读取 id=1 的数据的,但它却读到了。

通过结果说明:我们可以在一个事务未进行 commit/rollback操作之前,另一个事务仍然可以读取到数据库中的数据,只不过是读取到的是其他事务未改变之前的数据。此处是利用了MVCC多数据做了多版本处理,读取的数据来源于快照。

什么是MVCC

  1. MVCCMulti-Version Concurrency Control,多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,
  2. 实现对数据库的并发访问;在编程语言中实现事务内存。

如果有人在读数据的同事,另外一个人同时在修改相同的数据,那么此时前者可能会读到脏数据,或者「半写」的数据。解决这种冲突最简单的办法就是加 X 锁,但如果加排他锁会造成较为严重的性能问题,会阻塞其他读事务的进行。而MVCC使用了多版本快照的机制解决了这一问题。写操作在完成之前,读操作读取到的都是之前的版本,写操作在提交之前,对于其他的读者来说是不可见的。

那么此处需要注意的点就是一下两个:

  • 在读写并发的过程中如何实现多版本
  • 如何清除旧版本数据

MVCC数据的逻辑插入

在Innodb中建表时,每个表都会有三个隐藏列,其中和MVCC有关的是

  • 数据行版本号(DB_TRX_ID)
  • 删除版本号(DB_ROLL_PT)(原博主没说,但从名字来看,删除版本号应该是个指针)

我们先往表中插入两条数据:

  1. begin;-- 获取到全局事务ID
  2. insert into `test_zq` (`id`, `test_id`) values('5','68');
  3. insert into `test_zq` (`id`, `test_id`) values('6','78');
  4. commit;-- 提交事务

image.png
可以看到,插入的过程中,会把全局的事务ID插入到数据行版本号中。

MVCC数据的逻辑删除

对上述数据做删除逻辑,执行以下SQL语句(假设此时事务ID为3)

  1. begin;--获得全局事务ID = 3
  2. delete test_zq where id = 6;
  3. commit;

执行完之后的数据如下:
image.png
可以看到,并没有直接将数据删除,而是将 DB_ROLL_PT 字段设置为3

MVCC数据的逻辑修改

执行以下SQL语句:

  1. begin;-- 获取全局系统事务ID 假设为 10
  2. update test_zq set test_id = 22 where id = 5;
  3. commit;

原数据ROLL_PT值被修改为10,并且重新插入一条TRX值为10的新数据
image.png

MVCC数据的逻辑查询

查询规则如下:

  • 查询 DB_TRX_ID <= current_transaction_NO ,查询当前事务开始前已经存在的数据,或者被当前事务修改过的数据
  • 查询 DB_ROLL_PT == NULL || DB_ROLL_PT > current_transaction_NO ,保证查询到的数据是在当前事务开始前没有被删除的