社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

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

唤之 • 7 年前 • 856 次点击  

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的博客然后结合自己的理解重新整理了一篇新的文章;

今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/rvMdgORaQz
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/5057
 
856 次点击