MVCC 是啥?
MVCC 的英文全称是 Multiversion Concurrency Control ,中文意思是多版本并发控制技术。原理是,通过数据行的多个版本管理来实现数据库的并发控制,简单来说就是保存数据的历史版本。可以通过比较版本号决定数据是否显示出来。读取数据的时候不需要加锁可以保证事务的隔离效果。
MVCC 可以解决什么问题?
- 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,读不相互阻塞,写不阻塞读,这样可以提升数据并发处理能力。
- 降低了死锁的概率,这个是因为 MVCC 采用了乐观锁的方式,读取数据时,不需要加锁,写操作,只需要锁定必要的行。
- 解决了一致性读的问题,当我们朝向某个数据库在时间点的快照是,只能看到这个时间点之前事务提交更新的结果,不能看到时间点之后事务提交的更新结果。
什么是快照读?
快照读,读取的是快照数据,不加锁的简单 Select 都属于快照读.
SELECT * FROM player WHERE ...
什么是当前读?
当前读就是读的是最新数据,而不是历史的数据,加锁的 SELECT,或者对数据进行增删改都会进行当前读。
SELECT * FROM player LOCK IN SHARE MODE;
SELECT FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...
MVCC的实现原理
MVCC的实现原理主要依赖于行记录中的隐藏列、undo log、一致性视图,通过undo log来保存多版本的数据,通过一致性视图来保存当前活跃的事务列表,将两者结合和制定一定的规则来判断当前可读数据。
行记录的隐藏列
- row_id :隐藏的行 ID ,用来生成默认的聚集索引。如果创建数据表时没指定聚集索引,这时 InnoDB 就会用这个隐藏 ID 来创建聚集索引。采用聚集索引的方式可以提升数据的查找效率。
- trx_id: 操作这个数据事务 ID ,也就是最后一个对数据插入或者更新的事务 ID 。
- roll_ptr:回滚指针,指向这个记录的 Undo Log 信息。
- flag:一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了。
Undo Log
InnoDB 将行记录快照保存在 Undo Log 里。通过链表的结构的串联了起来,每个快照都保存了 trx_id 事务ID,undo log成为一条记录版本线性表(链表),undo log的链首就是最新的记录,链尾就是最早的旧记录。如果要找到历史快照,就可以通过遍历回滚指针的方式进行查找。
Read View 是啥?
如果一个事务要查询行记录,需要读取哪个版本的行记录呢? Read View 就是来解决这个问题的。Read View 可以帮助我们解决可见性问题。 Read View 保存了当前事务开启时所有活跃的事务列表。换个角度,可以理解为: Read View 保存了不应该让这个事务看到的其他事务 ID 列表。
- trx_ids 系统当前正在活跃的事务ID集合。
- low_limit_id ,活跃事务的最大的事务 ID。
- up_limit_id 活跃的事务中最小的事务 ID。
- creator_trx_id,创建这个 ReadView 的事务ID。
如果当前事务的 creator_trx_id 想要读取某个行记录,这个行记录ID 的trx_id ,这样会有以下的情况:
- 如果 trx_id < 活跃的最小事务ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建前就已经提交了,那么这个行记录对当前事务是可见的,通过undo log回退到小于trx_id的快照版本。
- 如果trx_id > 活跃的最大事务ID(low_limit_id),这个说明行记录在这些活跃的事务之后才创建,说明这个行记录对当前事务是不可见的。
- 如果 up_limit_id < trx_id <low_limit_id,说明该记录需要在 trx_ids 集合中,可能还处于活跃状态,因此我们需要在 trx_ids 集合中遍历 ,如果trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见,否则 ,trx_id 不存在于 trx_ids 集合中,说明事务trx_id 已经提交了,这行记录是可见的。
如何查询一条记录
- 获取事务自己的版本号,即 事务ID
- 获取 Read View
- 查询得到的数据,然后 Read View 中的事务版本号进行比较。
- 如果不符合 ReadView 规则, 那么就需要 UndoLog 中历史快照;
- 最后返回符合规则的数据
InnoDB 实现多版本控制 (MVCC)是通过 ReadView+ UndoLog 实现的,UndoLog 保存了历史快照,ReadView 规则帮助判断当前版本的数据是否可见。
总结
- 如果事务隔离级别是 ReadCommit ,一个事务的每一次 Select 都会去查一次ReadView ,每次查询的Read View 不同,就可能会造成不可重复读或者幻读的情况。
- 如果事务的隔离级别是可重读,为了避免不可重读读,一个事务只在第一次 Select 的时候会获取一次Read View ,然后后面索引的Select 会复用这个 ReadView.
参考列表:
https://zhuanlan.zhihu.com/p/231947511
https://zhuanlan.zhihu.com/p/147372839