默认的 MySQL 隔离级别是 RR ( Repeatable Read ), 而 MySQL 底层通过 MVCC 机制 实现了 RR ,并且解决了 一部分的幻读问题,今天我们来深入理解一下 MySQL 底层是如何实现 MVCC 的。
MVCC
MVCC (Multiversion Concurrency Control) 也就是 多版本并发版本控制,简单点来说就是 多个事务查询同一批数据时只能查看到当前事务可查看的数据。
MVCC 主要通过 undolog 版本链 和 readview 来完成,以下来逐一说明一下 它们的用处
UndoLog 版本链
首先说明一个前置知识点,其实每一条数据都有了两个隐藏字段(其实不止两个), 一个是 trx_id , 一个是 roll_pointer。
- trx_id: 最近更新这条数据的事务id
- roll_pointer:指向更新这个事务之前生成的 undolog
MySQL 在执行每一个操作之前,会先插入一条 undolog ,如果是 insert 则插入 delete ,如果是 update ,则将 update 之前的数据插入 undolog,以此达到 回滚的目的。 那 undolog 的数据中,还会有一个额外的字段,那就是 事务版本号(trx_id),这个版本号是递增的,随着事务数量的增多,版本号也会越来越大。由此也能组成一个 undolog 的版本链。
而有了事务版本链,也就能解决一个简单的问题,也就是 当前事务,只看当前事务的数据,不会看到别的事务的数据。但它还没办法解决 ,可重复阅读的问题。例如,事务A 查询了 id=1 的数据,这时候 事务B来了,修改了 id=1的数据,并提交了。这时候 事务A 应该查看到 事务B 最新提交的数据吗? 在 RR 的隔离级别下,是不应该的,而这个问题单靠 UndoLog 版本链是无法很好的解决的,这时候就需要 ReadView 出马了。
ReadView
在 MySQL中在事务执行第一个SELECT语句时都会生成一个 ReadVIew,ReadView 中包含 四个字段
- m_ids(trx_ids):当前激活的事务ids
- low_limit_id: 当前激活的事务中最小的id
- up_limit_id:下一个事务id,也就是最大的事务id
- creator_trx_id:当前的事务id 而这个 ReadView 生成以后 就不会再进行改变了。
假设
- 有一条数据
id=1,age=18,trx_id=1 - 首先 事务A 进来了 它的ReadView 是
{ m_ids:[ ], min_trx_id:2, max_trx_id:3,creator_trx_id:2 }-> 这表示当前除了它自己并没有其他的活跃事务, 表明它可以查看 trx_id 小于等于 2 的数据。 - 然后 事务B 进来了 它的ReadView 是
{ m_ids:[ 2 ], min_trx_id:2, max_trx_id:4,creator_trx_id:3 }-> 这表示当前已有1个活跃事务 [ 2 ] , 这时候它可以查看 trx_id 小于等于 3 且事务id不包含在 m_ids 中的数据。
这时候,事务B 将 数据修改为 age=19 ,那么 此时数据就变成 id=1,age=18,trx_id=3 。 若事务A 查询,则会发现 这个 trx_id 不满足它的查看要求,则会向上搜索 undoLog 版本链,最终找到原始数据 id=1,age=18,trx_id=1 并返回。
- 这时候 事务C 进来了 它的ReadView 是
{ m_ids:[ 2,3 ], min_trx_id:2, max_trx_id:5,creator_trx_id:4 }-> 这表示当前已有2个活跃事务 [ 2,3] ,则表示 它可以查看 trx_id 小于等于 4且事务id不包含在 m_ids 中的数据。 若事务C要查询,则会发现 这条数据的 trx_id:3 在自己的 m_ids列表中,表示这是当前活跃事务的数据,则不会采用这条数据,会向上搜索 undoLog 版本链,最终找到原始数据 id=1,age=18,trx_id=1 并返回。
RC 是如何实现的
说完了RR下的MVCC,可以顺带说一下 RC 下的 MVCC 是怎么样的。在RC隔离级别下,事务的每次查询都会生成一次 ReadView,这样就可以实现每次在事务提交后 ,查询到最新事务的数据了。
以上就是 MySQL 的MVCC 的基本原理,通过 undoLog 版本链 和 ReadView 便可方便快捷的在多事务环境下保证数据的安全性。