mvcc到底是什么?可以解决快照读的幻读问题吗?

118 阅读3分钟

| 持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
曾几何时,我因为不思考,只死记硬背生搬硬套,把mvcc看作是“解决快照读的幻读问题”,让面试官大跌眼镜、大失所望、大吃一惊。面试官说okok快住嘴,下去看看相关资料我们在讨论。okok,我闭嘴。
所以,mvcc到底是干嘛的?

mvcc

mvcc的实现基于undo log,通过回滚段保存undo log记录版本快照数据,通过readView机制确定数据的可见性。

  1. 对于数据的每次更新/插入会记录在undo log中,每条undo日志里有一个roll_pointer属性,所有版本会通过roll_pointer属性链接成链表,即版本链,版本链的头节点是当前记录最新的值。

  2. innodb每行数据的隐藏列包含了本行数据的trx_id和指向undo log的指针。

  3. readView主要用于可见性判断

    1. 可重复读隔离机制下,会在事务开启后第一个select读操作后创建readView快照

    2. 提交读隔离机制下,事务的每一个select读操作都会创建readView快照。

    3. 其中包含四个内容。

      1. m_ids,表示生成ReadView时当前系统中活跃的读写事务的事务id列表
      2. min_trx_id:表示生成ReadView时当前系统中活跃的读写事务中最小的事务id
      3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id
      4. creator_trx_id:表示生成该ReadView事务的事务id

当被访问版本的trx_id与readView中creator_trx_id相同,当前事务正在访问自己修改的记录,可以直接访问。

当被访问版本的trx_id比readView中min_trx_id小,表明被访问的事务已经提交,所以该版本可以被访问

当被访问版本的trx_id比readView中max_trx_id大,表明被访问的事务还没提交,所以该版本不可以被访问。

当被访问版本的trx_id在readView的min_trx_id和max_trx_id中,需要判断trx_id是否在m_ids列表中,如果在,说明生成readView时该版本事务仍然活跃,不可以被访问;如果不在,说明创建readView时该版本事务已经被提交,可以被访问。

如果某个版本的数据对于当前事务不可见,就可以通过undo log版本链寻找下一个版本的数据,继续以上流程寻找。

快照读取数据的可见版本。而当前读通过加锁读取数据的最新版本。可重复读隔离级别的幻读建立在当前读的情况,因此使用next-key(行锁+间隙锁)来解决,防止其他事务插入新数据。

幻读指当某个事务在读取某个范围内记录时,另外一个事务又在该范围内插入新的纪录,且之前的事务再次读取该范围的纪录会产生幻行。

截屏2022-10-24 下午6.40.14.png

image.png 此时 trx_1 的第二句 select * from users where id = 2 是可以查询到 id=2 的记录的,也就是出现了幻读。按可见性分析就是这条记录的 trx_id=1 = m_creator_trx_id=1,也就是访问的是当前事务修改的版本,所以当前事务可见。所以 MVCC 并不能完全解决幻读的问题,那应该要怎么解决呢,答案就是由程序自己来解决,需要语句进行加锁来保证不出现幻读,也就是 MVCC 和 MySql 的间隙锁进行配合来保证不出现幻读,并不是由 MVCC 天然来保证的。

参考

dev.mysql.com/doc/refman/…
blog.csdn.net/weixin_4175…

juejin.cn/post/705507…

mysql.taobao.org/monthly/201…