草庐IT

「MySQL高级篇」MySQL之MVCC实现原理&&事务隔离级别的实现

MeloJun 2023-03-28 原文

大家好,我是melo,一名大三后台练习生,死去的MVCC突然开始拷打我???!

?引言

MVCC,非常顺口的一个词,翻译起来却不是特别顺口:多版本并发控制。

  • 其中多版本是指什么呢?一条记录的多个版本。
  • 并发控制?如何实现呢?我们上篇刚讲到了锁机制,而MVCC则是用更好的方式来提高并发性能,避免加锁!具体如何实现,底层原理是什么,这篇将带你攻破ta。

?本篇速览脑图


通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

看完后文,再回过头来看这张图,就会理解了

当前读,快照读

首先我们需要一些前置知识,区分开当前读和快照读。

  1. 加锁的读,则是当前读,另外update,insert,delete也都是当前读
  2. 快照读,我们平时简单的select语句其实就是【不加锁】

注意串行化隔离级别下,快照读会退化为当前读。

  • 那这俩跟MVCC有什么关系呢?

快照读,相当于你可以读到的是一个历史版本,维护这些历史版本就需要MVCC出马了【其中的undolog版本链】

MVCC用处

解决 读—写 冲突的无锁并发控制,每次对A记录的写操作,都会给A保存一个快照版本,至于读操作的时候,读的是哪个快照版本,这就得看MVCC的实现原理了【下文的readview访问规则】

?MVCC实现原理

?记录中的隐藏字段

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的

每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id【也就是下图的DB_TRX_ID】。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除

  • DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空

  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

?readview

四个核心字段


计算m_ids的时候,可能会有新的事务产生,为了防止这种情况出现,MySQL保证计算m_ids【也就是生成视图数组的时候】会在事务系统的锁保护下进行,是原子操作,期间不会创建新的事务。

??访问规则

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见

  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见

  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,表明这个版本的记录在创建 Read View 的时候 可能处于“活动状态”或者“已提交状态”;需要判断 trx_id 是否在 m_ids 列表【活跃状态】中:--【因为是有序的,故采用二分查找】

    • 如果记录的 trx_id m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见

??总结

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。

??update特例


在这个例子中,如果还按上边的访问规则来看的话,应该是读取不到102这个版本来着,但实际情况是如何呢?

如果读取不到的话:那事务B还是在原来的k基础上去+1,那么事务C的更新相当于是丢失了!

这里就涉及到了我们开篇讲到的当前读,更新数据都是先读后写的,这个读,就是“当前读”。

而且当前读需要对数据行加锁,此处由于事务C已经提交了,释放了锁【两阶段协议】,因此事务B可以直接查到,若事务C还未提交的话,还需要阻塞等待。

?‍♂️?‍♂️45讲疑问

可能看了45讲的小伙伴会有疑问,45讲里边这个图

这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  3. 如果落在黄色部分,那就包括两种情况
    a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

这个图很容易迷惑到我们,让我们误以为黄色部分跟未提交事务集合是等同的,那怎么落在黄色部分里边,还能再细分成两种情况呢?

melo画了个花里胡哨的图,来看看计算的过程【如有错误之处还请指正】

  1. 1-10就是45讲里边的绿色部分,11-15是黄色部分,15之后是红色部分
    1. 如此可以看到,黄色部分里边,还是有一些不在m_ids里边的吧,不要被表面的图像迷惑了
    2. 并不是说只有11之前的,才是已提交事务,11-15里边也是可能会有已提交事务的


生成时机

注意,并不是开启事务就生成了,得执行快照读了才会

RC: 在事务中每一次执行快照读都会生成
RR:仅在事务中第一次执行快照时生成,后续都是复用这个readview
但是如果事务中进行了当前读的操作,比如事务中进行了update操作,后续再查询就会重新生成ReadView

其实就是上边的update特例

?undo log

当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现快照读

类型


在 InnoDB 存储引擎中 undo log 分为两种: insert undo log 和 update undo log:

  1. insert undo log :指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见【只在事务回滚时需要】,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作
  2. update undo log :update 或 delete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表【下文的版本链】,等待 purge线程 进行最后的删除

?版本链

类似一个链表,通过回滚指针,串联起来

  • 链表头部是最新的数据,尾部是最旧的记录


?栗子

??RC的例子

快照读


先看事务5里边,两次快照读生成的readview是怎样的?

  1. 第一次执行,此时活跃的事务id有【3,4,5】(2已经提交了)
  2. 最小即是3,最大【注意是预分配最大】是6
  3. 创建该事务的id自然是5

第二次快照读也是同样的分析方式

?判断能查到哪个事务记录

我们想知道第一次快照读,读取到的是哪个事务对应的记录【左下角中四个记录】

比如拿 0x0003这条记录来分析,trx_id是3,去跟第一个readview比对

  1. 判断是否是当前事务创建的记录,3!=5,说明不是
  2. 判断是否已经提交了【小于min_trx_id】,3不小于3,则还未提交
  3. 判断是否是创建readview之后才创建的事务记录【大于max_trx_id】,3不大于,则不是
  4. 判断数据是否已经提交【不在m_ids】里边,3在说明还未提交

因此,第一次快照读,是没法读取到 0x0003这条记录的


RR的例子


具体如何分析,跟上边RC是一样的,这里就不再赘述

只需要注意:如果期间出现了当前读,则会重新生成readview

总结

MVCC就是为快照读而生的,维护不同的快照版本,使得不同事务的读-写操作不会冲突,实现多版本并发控制,借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别

?下篇预告

这篇我们主要讲的是MVCC多版本并发控制,结合了事务的隔离级别,而关于事务背后的原理相关的日志,这些我们留到后边再来详解。

?参考文献

  • MySQL45讲
  • 黑马MySQL视频

收藏=白嫖,点赞+关注才是真爱!!!本篇文章如有不对之处,还请在评论区指出,欢迎添加我的微信一起交流:Melo__Jun

?友链

有关「MySQL高级篇」MySQL之MVCC实现原理&&事务隔离级别的实现的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  4. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  5. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  6. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  9. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  10. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

随机推荐