InnoDB存储引擎——MVCC机制

背景

多事务并发的时候会有4种并发场景:读读,读写,写读,写写;最早的数据库只支持读读并发,其他场景都要阻塞执行,为了提高数据库的并发性能,InnoDB 存储引擎引入了 MVCC,使得读-写,写-读操作可以并发执行。

MVCC(Multi-Version Concurrency Control)多版本并发控制,是一种并发控制的方法,一般在数据库中,实现对数据库的并发访问,提高吞吐量。

实现原理

MVCC也被称为快照读。在Read Committed 和 Repeatable Read 这两种隔离级别下使用

MVCC的实现是基于 undo log 版本链 和 ReadView 机制 来实现的。

版本链

每条数据有两个隐藏字段:trx_idroll_pointer

  • trx_id:最近一次更新这条数据的事务id
  • roll_pointer:指向更新这个事务之前生成的 undo log

例如,有一个事务A(事务id=10),往一张表里插入了一条数据,值为 A,此时数据存储如下:
在这里插入图片描述
接着,有一个事务B(事务id=15)修改了这条数据,把值改为了B,在数据修改之前生成一个 undo log 记录修改之前的数据,当前数据的 roll_pointer 指向 undo log。
在这里插入图片描述
假设,又有一个事务C(事务id=20)修改了这条记录,把值改成了C,然后生成一条 undo log,记录事务B修改的值
在这里插入图片描述
一条数据在经过多次更新后,每次更新数据都会生成一个undo log ,记录更新之前的数据,每一个 undo log 就是它不同时间版本的历史数据,用 roll_pointer 将 undo log 串在一起形成了一个链表,这个链表就称为版本链。

ReadView

ReadView 解决的问题是使用 RC 和 RR 隔离级别的事务中,不能读到未提交的记录,这需要判断版本链中的哪个版本是当前事务可见的。
ReadView中有4个比较重要的内容:

  • m_ids:生成 ReadView 的时候,系统当前活跃的事务 id 集合
  • min_trx_id:活跃的事务id 集合中最小的事务id
  • max_trx_id:生成ReadView的时候,系统分配给下一个事务的id
  • creator_trx_id:生成ReadView的事务的事务id
    在这里插入图片描述
    事务可见性规则:
  • 如果版本链中的 trx_id 和 ReadView 中的 creator_trx_id 相同,表示当前事务读的是被自己修改过的记录,该版本对当前事务可见
  • 如果版本链中的 trx_id < min_trx_id,表示生成该版本的事务在生成ReadView之前提交了,所以该版本对当前事务可见
  • 如果版本链中的 trx_id > max_trx_id,表示生成该版本的事务在生成ReadView之后才生成,所以该版本对当前事务不可见
  • 如果版本链中的 trx_id 在 min_trx_id 和 max_trx_id 之间
    • 如果 trx_id 在 m_ids 列表中,说明创建 ReadView 时生成的事务还是活跃的,该版本对当前事务不可见
    • 如果 trx_id 不在 m_ids 列表中,说明创建ReadView时生成的事务已经提交,该版本对当前事务可见
RC 每次读取数据生成一个ReadView

数据库有如下一条数据,值为苹果,上一次修改这条数据的事务id=80
在这里插入图片描述
此时有一个事务B,事务id=100,将苹果修改为了香蕉
在这里插入图片描述
同时,还有个事务C,id=150,执行了 select 操作,查询过程如下:

  • 生成一个ReadView,m_ids=[100,150],当前有2个事务是活跃的
  • 版本链数据事务id=100,在m_ids列表中,不符合可见性规则,根据 roll_pointer 继续往下找
  • 找到版本数据事务id=80,小于 min_trx_id = 100,这条数据对事务C可见
  • 查询出结果苹果

事务A执行commit操作,提交事务,此时事务C,id=150,执行第二次 select 操作,查询过程如下:

  • 再生成一个ReadView,m_ids=[150],当前有1个事务是活跃的,事务A已经提交了,则不在 m_ids 列表里了
  • 版本链数据事务id=100,小于 min_trx_id = 150,这条数据对事务C可见
  • 查询出结果香蕉
RR 第一次读取数据时生成一个 ReadView

第一次查询的结果和 RC一样,查询出的结果为苹果
我们从第二次查询开始说起

事务A执行commit操作,提交事务,此时事务C,id=150,执行第二次 select 操作,查询过程如下:

  • 由于第一次查询已经生成了ReadView,第二次查询的时候不再重新生成ReadView
  • 第一次生成的ReadView中,m_ids=[100,150]
  • 版本链事务id=100,在m_ids列表中,不符合可见性规则,根据 roll_pointer继续往下找
  • 找到版本数据事务id=80,小于 min_trx_id = 100,这条数据对事务C可见
  • 查询出结果苹果
快照读失效了?

数据库中有一条记录,id=1,num=8
有2个事务按下步骤操作:

  • 事务B先查询一下数据,num=8
# 事务B
# 查询结果为 8
mysql> select * from tb_test where id=1;
+----+------+
| id | num  |
+----+------+
|  1 |    8 |
+----+------+
1 row in set (0.00 sec)
  • 事务A将值+1
# 事务A
mysql> update tb_test set num=num+1 where id=1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0
  • 事务B再查询一下数据,num = 8
# 事务B
mysql> select * from tb_test where id=1;
+----+------+
| id | num  |
+----+------+
|  1 |    8 |
+----+------+
1 row in set (0.01 sec)
  • 事务B执行update操作后再去查询,num = 10
mysql> update tb_test set num=num+1 where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from tb_test where id=1;
+----+------+
| id | num  |
+----+------+
|  1 |   10 |
+----+------+
1 row in set (0.00 sec)

我们看到结果值为10,和预期的结果9好像有些出入,其实不是的,执行update的时候,执行的是当前读,读到的是最新的数据9,并且对读取的记录加锁,阻塞其他事务同时改动相同记录,避免出现并发安全问题。

总结

在 RC 和 RR 隔离级别下,通过MVCC实现了并发读-写操作,提高了数据库的并发能力
RR 能做到可重复读,与 RC 最大的不同是在生成的 ReadView 的时机不同,RR 在同一个事务下只在第一次 select 中生成 ReadView,RC 则在每一个查询都会生成 ReadView
并不能解决写写并发的问题,写写并发需要通过加锁操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值