全网最全一篇数据库MVCC详解,不全你打我

什么是MVCC全称Multi-Version Concurrency Control , 即多版本并发控制 , 主要是为了提高数据库的并发性能 。以下文章都是围绕InnoDB引擎来讲 , 因为myIsam不支持事务 。
同一行数据平时发生读写请求时 , 会上锁阻塞住 。但mvcc用更好的方式去处理读—写请求 , 做到在发生读—写请求冲突时不用加锁 。
这个读是指的快照读 , 而不是当前读 , 当前读是一种加锁操作 , 是悲观锁 。
那它到底是怎么做到读—写不用加锁的 , 快照读和当前读又是什么鬼 , 跟着你们的贴心老哥 , 继续往下看 。
当前读、快照读都是什么鬼什么是MySQL InnoDB下的当前读和快照读?
当前读它读取的数据库记录 , 都是当前最新的版本 , 会对当前读取的数据进行加锁 , 防止其他事务修改数据 。是悲观锁的一种操作 。
如下操作都是当前读:

  • select lock in share mode (共享锁)
  • select for update (排他锁)
  • update (排他锁)
  • insert (排他锁)
  • delete (排他锁)
  • 串行化事务隔离级别
快照读快照读的实现是基于多版本并发控制 , 即MVCC , 既然是多版本 , 那么快照读读到的数据不一定是当前最新的数据 , 有可能是之前历史版本的数据 。
如下操作是快照读:
  • 不加锁的select操作(注:事务级别不是串行化)
快照读与mvcc的关系MVCCC是“维持一个数据的多个版本 , 使读写操作没有冲突”的一个抽象概念 。
这个概念需要具体功能去实现 , 这个具体实现就是快照读 。(具体实现下面讲)
听完贴心老哥的讲解 , 是不是瞬间茅塞顿开 。
数据库并发场景
  • 读-读:不存在任何问题 , 也不需要并发控制
  • 读-写:有线程安全问题 , 可能会造成事务隔离性问题 , 可能遇到脏读 , 幻读 , 不可重复读
  • 写-写:有线程安全问题 , 可能会存在更新丢失问题 , 比如第一类更新丢失 , 第二类更新丢失
MVCC解决并发哪些问题?mvcc用来解决读—写冲突的无锁并发控制 , 就是为事务分配单向增长的时间戳 。为每个数据修改保存一个版本 , 版本与事务时间戳相关联 。
读操作只读取该事务开始前的数据库快照 。
解决问题如下:
  • 并发读-写时:可以做到读操作不阻塞写操作 , 同时写操作也不会阻塞读操作 。
  • 解决脏读、幻读、不可重复读等事务隔离问题 , 但不能解决上面的写-写 更新丢失问题 。
因此有了下面提高并发性能的组合拳:
  • MVCC + 悲观锁:MVCC解决读写冲突 , 悲观锁解决写写冲突
  • MVCC + 乐观锁:MVCC解决读写冲突 , 乐观锁解决写写冲突
MVCC的实现原理它的实现原理主要是版本链 , undo日志  , Read View 来实现的
版本链我们数据库中的每行数据 , 除了我们肉眼看见的数据 , 还有几个隐藏字段 , 得开天眼才能看到 。分别是db_trx_id、db_roll_pointer、db_row_id 。
  • db_trx_id 6byte , 最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID 。
  • db_roll_pointer(版本链关键) 7byte , 回滚指针 , 指向这条记录的上一个版本(存储于rollback segment里)
  • db_row_id 6byte , 隐含的自增ID(隐藏主键) , 如果数据表没有主键 , InnoDB会自动以db_row_id产生一个聚簇索引 。
  • 实际还有一个删除flag隐藏字段, 记录被更新或删除并不代表真的删除 , 而是删除flag变了
 
全网最全一篇数据库MVCC详解,不全你打我

文章插图
 
如上图 , db_row_id是数据库默认为该行记录生成的唯一隐式主键 , db_trx_id是当前操作该记录的事务ID , 而db_roll_pointer是一个回滚指针 , 用于配合undo日志 , 指向上一个旧版本 。
每次对数据库记录进行改动 , 都会记录一条undo日志 , 每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性 , 因为该记录并没有更早的版本) , 可以将这些undo日志都连起来 , 串成一个链表 , 所以现在的情况就像下图一样:


推荐阅读