MySQL底层之MVCC、回滚段、一致性读、锁定读( 二 )


一致性读的实现方式:

  1. 每个事务启动的瞬间 , 都会构建一个数组(m_ids) , 用来记录目前所有“活跃事务”(事务启动了 , 但是还没提交)的 ID;
  2. 数组中的最小事务ID为低水位;
  3. 数组中的最大事务ID+1为高水位;
  4. 数据版本可见性规则:当前数据某个版本是否可见 , 取决于当前数据的DB_TRX_ID以及这个一致性视图数组中记录的事务ID做对比来判断:低水位以前的数据版本可见 , 高水位以后的数据版本不可见 , 低水位和高水位之间得查看当前数据版本的DB_TRX_ID是否存在数组中 , 若存在意味着事务未提交 , 不可见 , 若不存在意味着事务已提交 , 可见 。

MySQL底层之MVCC、回滚段、一致性读、锁定读

文章插图
 
那按照一致性读的理解 , 事务B已经创建了自己的快照数据了 , 它的输出应该是num = 2呀 , 为什么会是num=3?
可是如果不是num=3 , 那么已经提交的事务C的操作不就丢失了吗?(产生丢失更新问题)
这里又涉及到一个知识点:
更新数据都是先读后写的 , 而这个读 , 只能读当前的值 , 称为“当前读”(current read) 。
3、当前读(current reads)也叫做锁定读(locking reads)
MySQL底层之MVCC、回滚段、一致性读、锁定读

文章插图
 
官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
InnoDB引擎支持两种方式的锁定读以提供额外的安全性(MySQL 5.7版本):
# 读锁(S 锁 , 共享锁)SELECT ... LOCK IN SHARE MODE;# 写锁(X 锁 , 排他锁)SELECT ... FOR UPDATE;锁定读会在被读取的数据上加一把共享锁 , 其他事务可以读取记录 , 但是不可以修改记录 , 直到当前事务提交 。
锁定读验证:
MySQL底层之MVCC、回滚段、一致性读、锁定读

文章插图
 
为什么要有锁定读?
如果你在一个事务中先查询了一个数据 , 然后插入或者更新相关的数据 , 这个时候来了一个事务B同时更新或者删除你要查询的记录 , 就会出现幻读问题了 。
这也是为什么MVCC不能完全解决幻读的问题 , 而是需要MVCC+行锁+间隙锁(next-key lock)的方式 。
4、事务A、B、C的执行流程继续看开头的第一张图:
MySQL底层之MVCC、回滚段、一致性读、锁定读

文章插图
 
start transaction with consistent snapshot;这条SQL语句可以立即启动事务 , 创建当前事务的一致性读快照 。效果等同于start transaction然后马上执行select语句 。
 
我们接下来看看文章开头的三个事务对数据行的修改流程 , 按照步骤1~6的操作如下:
MySQL底层之MVCC、回滚段、一致性读、锁定读

文章插图
 
如果大家细致的查看上图的三个事务的穿插执行流程 , 可以发现 , A、B、C三个事务无论是commit还是rollback , 都是可以最终得到正确的数据 。
这就是InnoDB引擎下的多版本并发控制(MVCC)的实现原理 。
总结以下几个关键点:
  1. 每一个事务都会创建一个数据快照 , 快照创建的时机根据隔离级别的不同有所区别;
  2. 每一个事务都会生成一个全局唯一的DB_TRX_ID , 用于标记当前版本;
  3. DB_ROLL_PTR是回滚指针的意思 , 结合DB_TRX_ID来最终确定我要拿到的数据;
  4. DB_TRX_ID、DB_ROLL_PTR、undo log这三个值来控制数据的版本;
  5. update、delete操作都是先读后写 , 这个读属于锁定读(当前读) 。
5、巨人的肩膀
  1. 《MySQL实战45讲》
  2. 《高性能MySQL 第二版》
  3. MySQL官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html
  4. 淘宝数据库内核月报 - 2017 / 12:http://mysql.taobao.org/monthly/2017/12/01/
最后 , 感谢大家能够看到这里 , 也许这篇文章并不能让你能够彻底了解MVCC的工作原理 , 看完之后可能还会有很多疑问 , 欢迎大家在评论区多多讨论 , 充分交流 , 共同学习 。




推荐阅读