MySQL-InnoDB-MVCC多版本并发控制

存储架构 2017-12-30

MVCC

(Multiversion Concurrency Control)

1.先引用《高性能MySQL》中对MVCC的部分介绍

  • MySQL的大多数事务型存储引擎实现的其实都不是简单的行级锁。 基于提升并发性能的考虑 , 它们一般都同时实现了多版本并发控制(MVCC)。不仅是MySQL, 包括Oracle,PostgreSQL等其他数据库系统也都实现了MVCC, 但各自的实现机制不尽相同, 因为MVCC没有一个统一的实现标准。
  • 可以认为MVCC是行级锁的一个变种, 但是它在很多情况下避免了加锁操作, 因此开销更低。虽然实现机制有所不同, 但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
  • MVCC的实现方式有多种, 典型的有乐观(optimistic)并发控制 和 悲观(pessimistic)并发控制。
  • MVCC只在 READ COMMITTEDREPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。

2.可以了解到:

  • MVCC是被Mysql中 事务型存储引擎InnoDB 所支持的;
  • 应对高并发事务, MVCC比单纯的加行锁更有效, 开销更小 ;
  • MVCC只在 READ COMMITTEDREPEATABLE READ 两个隔离级别下工作;
  • MVCC可以使用 乐观(optimistic)锁悲观(pessimistic)锁 来实现;

3.另外, 《高性能Mysql》中提到, InnoDB的MVCC是通过在每行记录后面保存 两个隐藏的列 来实现的..... 这个貌似和网上很多观点不同, 具体可以参考 MySQL官方对InnoDB-MVCC的解释

可以看到, InnoDB存储引擎在数据库每行数据的后面添加了三个字段, 不是两个!!

分析

1.InnoDB存储引擎在数据库每行数据的后面添加了三个字段

  • 6字节的事务ID( DB_TRX_ID )字段: 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1
    另外,删除在内部被视为一个更新,其中行中的特殊位被设置为将其标记为已删除
  • 7字节的回滚指针( DB_ROLL_PTR )字段 : 指向当前记录项的rollback segment的 undo log (撤销日志记录), 找之前版本的数据就是通过这个指针。
  • 6字节的 DB_ROW_ID 字段: 当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值,这个用于索引当中。
    结合聚簇索引的相关知识点, 我的理解是, 如果我们的表中有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 但聚簇索引会使用DB_ROW_ID的值来作为主键; 如果我们有自己的主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID 了 。
    关于聚簇索引, 《高性能MySQL》中的篇幅对我来说已经够用了, 稍后会整理一下以前的学习笔记, 然后更新上来。

2.下面来演示一下事务对某行记录的更新过程:

3. read view

  • 判断当前版本数据项是否可见
  • 在innodb中, 每创建一个新事务, 存储引擎都会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本( read view ), 副本中保存的是系统中当前不应该被本事务看到的其他事务id列表。
  • 当用户在事务中要读取某行记录的时候, innodb会将该行当前的版本号与该read view进行比较, 下面介绍 比较算法 ;

比较算法:

设该行的当前事务id为 trx_id_current , read view 中该行最早的事务id为 trx_id_first , 最迟的事务id为 trx_id_last

  1. 如果 trx_id_current < trx_id_first , 那就表示
    当前事务在读取该行记录的时候, 给该行数据设置的隐藏事务ID字段的值, 比 read view 中记录的 '当前系统中其他事务给该行记录设置的事务ID都要小'。
    这就意味着, 当前所有和该行记录有关的事务中, 当前事务是第一个读取到该行记录的, 没有任何在当前事务前面对该行数据做过更改但还没有提交的事务, 所以当前事务可以直接拿到表中 稳定的数据 !
  2. 如果 trx_id_current > trx_id_last 的话,那就表示
    当前事务在读取该行记录的时候, 给该行数据设置的隐藏事务ID字段的值, 比 read view 中记录的 '当前系统中其他事务给该行记录设置的事务ID都要大'。
    这就意味着, 当前所有和该行记录有关的事务中, 当前事务是最后一个读取到该行记录的, 所以需要从该行记录的 DB_ROLL_PTR 指针所指向的回滚段中取出最新的undo-log的版本号, 将它赋值给 trx_id_current ,然后继续重新开始整套比较算法, 这么迭代下去, 会在undo-log中一层层往下找下去, 最终就会取到 稳定的数据 !
  3. 如果 trx_id_first < trx_id_current < trx_id_last , 同上;

对比 READ COMMITEDREPEATABLE READ

  1. read view 生成原则如果想深入了解的话可以自行百度或者参考 fxliutao的博客
  2. 之前已经了解到 MVCC只在 READ COMMITTEDREPEATABLE READ 两个隔离级别下工作;
  3. 并且根据 read view 的生成原则, 导致在这两个不同隔离级别下, read committed 总是读最新一份快照数据, 而repeatable read 读事务开始时的行数据版本;

    • 使得 READ COMMITED 级别能够保证, 只要是当前 语句执行前 已经提交的数据都是可见的**。注意和 REPEATABLE READ 级别的区!!!
    • 使得 REPEATABLE READ 级别能够保证, 只要是当前 事务执行前 已经提交的数据都是可见的。

小结

  1. 一般我们认为MVCC有下面几个特点:

    • 每行数据都存在一个版本,每次数据更新时都更新该版本
    • 修改时Copy出当前版本, 然后随意修改,各个事务之间无干扰
    • 保存时比较版本号,如果成功(commit),则覆盖原记录, 失败则放弃copy(rollback)
    • 就是每行都有版本号,保存时根据版本号决定是否成功, 听起来含有乐观锁的味道, 因为这看起来正是,在提交的时候才能知道到底能否提交成功
  2. 而InnoDB实现MVCC的方式是:

    • 事务以排他锁的形式修改原始数据
    • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
    • 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
  3. 二者最本质的区别是 : 当修改数据时是否要 排他锁定 ,如果锁定了还算不算是MVCC?
  • Innodb的实现真算不上MVCC, 因为并没有实现核心的多版本共存, undo log 中的内容只是串行化的结果, 记录了多个事务的过程, 不属于多版本共存。但理想的MVCC是难以实现的, 当事务仅修改一行记录使用理想的MVCC模式是没有问题的, 可以通过比较版本号进行回滚, 但当事务影响到多行数据时, 理想的MVCC就无能为力了。
  • 比如, 如果事务A执行理想的MVCC, 修改Row1成功, 而修改Row2失败, 此时需要回滚Row1, 但因为Row1没有被锁定, 其数据可能又被事务B所修改, 如果此时回滚Row1的内容,则会破坏事务B的修改结果,导致事务B违反ACID。 这也正是所谓的 第一类更新丢失 的情况。
  • 也正是因为InnoDB使用的MVCC中结合了排他锁, 不是纯的MVCC, 所以第一类更新丢失是不会出现了, 一般说更新丢失都是指第二类丢失更新。

本文主要参考和引用如下文章

MySQL官方对InnoDB-MVCC的解释

fxliutao的博客 然后结合自己的理解重新整理了一篇新的文章;

SegmentFault

责编内容by:SegmentFault (源链)。感谢您的支持!

您可能感兴趣的

MySQL引擎特性:InnoDB IO子系统 前言 InnoDB做为一款成熟的跨平台数据库引擎,其实现了一套高效易用的IO接口,包括同步异步IO,IO合并等。本文简单介绍一下其内部实现,主要的代码集中在os0file.cc这个文件中...
MySQL InnoDB锁机制 概述: 锁机制在程序中是最常用的机制之一,当一个程序需要多线程并行访问同一资源时,为了避免一致性问题,通常采用锁机制来处理。在数据库的操作中也有相同的问题,当两个线程同时对一条数据进行操作,为...
Sysbench: in-memory, InnoDB and a small server, My... This has results for in-memory sysbench with InnoDB and MySQL versions 5.0, 5.1, 5.5, 5.6, 5.7 and ...
MySQL Innodb 如何找出阻塞事务源头 SQL 在MySQL数据库中出现了阻塞问题,如何快速查找定位问题根源?在实验开始前,我们先梳理一下有什么工具或命令查看MySQL的阻塞,另外,我们也要一一对比其优劣,因为有些命令可能在实际环境下可能并不...
MySQL Partitioning This Blog Post is an overview of MySQL Partitioning and its operations, Will explore following point...