美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制)

这篇具有很好参考价值的文章主要介绍了美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。

原文:美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制)

“全文共计4270字,预计阅读时间6分钟

大家好,我是tin,这是我的第26篇原创文章

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

还记得MySQL数据库事务都有哪些隔离级别么?如果忘记可以到这里重新温习:还傻傻搞不懂MySQL事务隔离级别么(图文并茂,保证你懂!)。

今天主要说一说MySQL是如何实现可重复读隔离级别的, 话不多说,先上一个目录:

一、MVCC的实现原理

    1.1 什么是MVCC

    1.2 两个隐式列

    1.3 undo日志 

    1.4 Read View读视图

二、可重复读实现原理

    2.1 什么是可重复读

    2.2 基于多版本实现可重复读

    2.3 可重复读可以解决幻读么

三、结语

一、MVCC的实现原理

在讲可重复读隔离级别之前,必须先来了解MVCC的实现原理,因为它的实现依赖MVCC,而MVCC的实现又依赖undo log和 Read View,下面一一讲解。

1.1 什么是MVCC

MVCC,直译多版本并发控制,它的全称是Multi-Version Concurrency Control, 直白说就是在同一时刻同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制。

在MySQL InnoDB中,MVCC的实现主要是为了提高数据库并发性能,它能很好地处理MySQL的读写冲突,做到尽量不加锁。

比如现在我的账户表中有这样一条Tom的余额记录:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

除了正常id,user_name,balance字段以外,其实还有两个隐藏字段,我已一并画出,后续会补充讲解。

如果同时有多个事务操作这行数据时,为了避免多事务并发场景下数据不一致问题的出现,InnoDB会通过一条 "链条"把每个事务的数据"快照版本"串联起来, 比 如下面这样:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

每个事务启动的时候都会给自己分配一个事务ID(trx_id)并生成一个当时所有活跃事务的事务ID列表(其实就是Read View,下文有讲到)。

当一个事务操作更新一条记录后,会同时把当前事务ID更新到这条数据记录上(隐藏字段)。当下一个事务要操作这条记录时,会先比较trx_id和自己的事务ID大小,同时和自己维护的活跃事务列表ID比较,如果不能直接读取记录行的数据,那么会顺着undo日志的"链条"找到自己能用的数据版本,这就是MVCC。

1.2 两个隐藏列

上文已经提到,MySQL每行记录都会有两个隐藏字段,一个是事务ID(trx_id),一个是回滚指针(roll_pointer)。

trx_id :每次一个事务对某条聚簇索引记录改动时,都会把自己的事务id重新写到 trx_id 隐藏列。

roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧版本记录写入到 undo日志中,roll_pointer这个隐藏列是一个地址指针,通过它可以找到该聚簇索引记录历史修改信息。

1.3 undo 日志

undo 日志:又名undo log,也称回滚日志,它是 Innodb 存储引擎层生成的日志。在数据更新之前,MySQL就需要先把更新前的数据记录到 undo log 日志中,当事务回滚时,可以利用 undo log 来进行回滚。

比如现在Tom的账户余额有100,现在有一个事务需要把Tom的账户余额更新为300,大致的流程如下图:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

undo log主要分为两种:

insert undo log:顾名思义,此代表执行insert语句时产生的undo log, 它只在事务回滚时需要,因为这种log只是对本事务可见,其他事务不可见,所以当事务提交后,这种类型的undo log就会被系统直接删除回收(也就是该undo log占用的undo页面链表被释放)。

update undo log:事务在进行update或者delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要(也就是MVCC),所以不能在事务提交后马上删除,只在提交后放入undo log的链表,等待purge线程进行最后的删除。

1.4 Read View读视图

什么是Read View?

说白了,Read View就是事务进行快照读操作的时候产生的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统数据以及当前活跃事务的ID(就是启动了还没提交的事务)。

什么是当前读和快照读?

当前读:使用到当前读的场景有select lock in share mode(共享锁)、 select for update 、 update、insert、delete(排他锁)等,这些操作都是一种当前读,因为它需要读取记录的最新版本,而且读取时还有可能会通过加锁保证其他事务不能同时修改当前记录。

快照读:快照读一般指不加锁的select操作,当然如果MySQL数据库的事务隔离级别是串行隔离级别(什么是串行隔离级别?忘了可以重新看这里:还傻傻搞不懂MySQL事务隔离级别么(图文并茂,保证你懂!)),串行级别下的快照读会退化成当前读。

一个Read View 包含以下四个字段:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

creator_trx_id :指创建该 Read View 的事务的事务 id。

m_ids :指创建该 Read View 时数据库中所有活跃事务的事务 id 列表,这是一个列表,活跃事务则代表是已启动但未提交的事务。

min_trx_id :指创建 Read View 时数据库中所有活跃事务中最小的事务 id,也就是 m_ids 中的最小值。

max_trx_id :指下一个要创建 Read View 的事务的事务 id,它并不是m_ids中的最大值,需要加以区分。

在可重复读隔离级别下,Read View是在事务开始(begin)之后且执行第一条sql时创建,创建Read View的同时也就生成了一个新的事务id(直到commit结束),事务会依赖该 Read View保证查询结果保持不变直到该事务结束。

二、可重复读实现原理

2.1 什么是可重复读

可重复读(REPEATABLE READ),指在整个事务过程中该事务看到的记录,自始至终都是一样的。

比如现在有两个活跃事务,事务A和事务B,在事务B给Tom账户余额增加100并提交后,事务A能马上看得见么?如下图:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

可重复读隔离级别下,事务A在提交前自始至终查到的值都必须一样,所以,也即使事务B更新了Tom的余额也一样。所以余额R1、R2都是100,当事务A提交后再查询(其实是新事务)才能查到新的值,所以R3是200。

之所以有这样的效果,是因为事务A一开始就创建好了Read View,一直到提交前都采用同一个Read View。

2.2 基于多版本实现可重复读

有了以上对隐藏列、undo log和Read View的了解,要分析MVCC的实现原理就简单多了。

假如现在Tom的账户余额有100,当前该记录上的事务id是10。

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

现在MySQL系统有且只有两个活跃事务,两个事务同时在操作Tom的账户,分别为事务A和事务B。事务A在事务B前启动,但事务A的第一条sql执行前事务B也已启动。

基于以上,事务A和事务B的Read View如下:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

为什么两个事务的m_ids都是[11, 12]?因为前面已经说过,RR隔离级别下的Read View是事务内第一个sql语句时才会创建。

在事务 A 的 Read View 中,它的事务 id 是 11,由于是在事务 B 启动后才创建,所以此时活跃的事务id就有 11 和 12,活跃的事务 id 中最小的事务 id 11,下一个事务 id 是 13。

在事务 B 的 Read View 中,因为它后启动于事务A,所以它的事务 id 是 12,当然此时活跃的事务的事务 id 有 11 和 12,活跃的事务 id 中最小的事务 id 还是11,下一个事务 id 依然是 13。

我先把事务A和事务B内部操作流程画出来:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

在时间线第③步,事务 A 查询Tom账户余额时,对应记录上的隐藏字段记录着事务id(trx_id) = 10,通过和事务 A 自己本身的 Read View 的 m_ids 字段对比可以发现,trx_id = 10并不在活跃事务的列表中,并且事务 A 的事务 id(11)比它还大,这就可以得出这条记录的事务(trx_id = 10)已经在事务A启动前提交过了,所以该记录直接对事务 A 可见,事务A查询Tom账户余额是100。

接着,在时间线第④步,事务B更新Tom账户余额,给Tom余额增加100,Tom的账户余额变成了200,这时 MySQL 会记录相应的历史版本undo log,并通过隐藏列roll_pointer指向该版本log的地址,形成版本链(更早的undo log则由新的一行undo log指向),如下图:

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

事务 B 修改了Tom的余额记录,Tom的余额记录就会变成200,同时事务id列(trx_id)记录的是事务B的事务id(12)。

回滚指针列(roll_pointer)则指向新生成的历史版本数据,也就是undo log,这样,最新记录和旧版本记录就通过链表的方式串起来了。

接着,到时间线的第⑤步,事务A继续查询Tom的余额,这时会发现Tom余额记录上的trx_id 为 12,比自己的事务 id(11) 还大,并且12还在活跃事务列表(m_ids)中,也比下一个事务 id(max_trx_id)小。

这时,事务 A 并不会读取这条记录,而是通过roll_pointer找到undo log上的旧版本数据,通过每个版本的roll_pointer一直往下找,直到找到 trx_id 等于或者小于自己的事务id且又不在自己Read View的m_ids列表的第一条记录。

最终,事务 A 读取到 trx_id = 10 的记录,也即查询Tom账户余额还是100。

通过以上的方式MySQL实现了可重复读隔离级别,总而言之,通过多版本并发控制(MVCC),让一个事务在整个事务生命周期内读到的数据保持了一致。

2.3 可重复读可解决幻读么

之前一直都讲,MySQL InnoDB引擎默认的事务隔离级别是可重复读隔离级别,那么既然是这样,可重复读级别解决了幻读问题么?

答案当然是可重复读没有解决幻读问题!

只是MySQL通过一种next-key lock的锁机制一定程度上避免了幻读问题。那MySQL又是如何避免幻读问题的呢?

写到最后发现这是一个需要长篇幅才能说清楚的问题,今天在这里就写啦,欢迎关注我的下一篇文章。

三、结语

我是tin,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。

看到这里请安排个“三连”(分享、点赞、在看)再走吧,坚持创作不容易,你的正反馈是我坚持输出的最强动力,谢谢!

美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制),mysql,java,后端

最后别忘了关注我哦!公众号【看点代码再上班】文章来源地址https://www.toymoban.com/news/detail-538793.html

到了这里,关于美团面试官:可重复读隔离级别实现原理是什么?(一文搞懂MVCC机制)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MySQL事务(4种事务隔离级别、脏写、脏读、不可重复读、幻读、当前读、快照读、MVCC、事务指标监控)

    显示事务: read write:读写事务,默认模式,表示当前事务可以读写数据。 read only:只读事务,很少用,表示当前事务不能修改数据。 with consistent snapshot:一致性快照,在数据库事务中确保事务在执行过程中能看到一个事务开始时的一致数据库状态,避免被其他并发操作影响

    2024年03月10日
    浏览(33)
  • MySQL数据库中,在读已提交和可重复读这两个不同事务隔离级别下幻读的区别

    在正式开始之前,先简单回顾一下并发事务存在的问题以及事务的隔离级别等内容。 1.1 并发事务存在的问题 当两个或者两个以上事务同时开启去处理同一个表的数据时,可能会存在以下的问题: 丢失修改 脏读 不可重复读 幻读 丢失修改 丢失修改是指当两个或多个事务更新

    2024年02月02日
    浏览(44)
  • 事务——什么是事务,事务的特性,事务的隔离级别

            事务就是用户定义的一系列操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。 典型场景:银行转账 A 转账100元给B,A账户减少100元,B账户增加100元; 如果A转出失败或者B转入失败(任意一方失败)

    2024年02月10日
    浏览(34)
  • 【MySQL】事务及其隔离性/隔离级别

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、事务的概念 1、事务的四种特性 2、事务的作用 3、存储引擎对事务的支持 4、事务的提交方式 二、事务的启动、回

    2024年02月12日
    浏览(36)
  • 美团面试:ES+Redis+MySQL高可用,如何实现?

    在40岁老架构师 尼恩的 读者交流群 (50+)中,尼恩一直在指导大家改造简历、指导面试。指导很多小伙伴拿到了一线互联网企业网易、美团、字节、如阿里、滴滴、极兔、有赞、希音、百度、美团的面试资格,拿到大厂offer。 前几天,指导了一个40岁老伙伴拿到年薪100W offer,

    2024年02月03日
    浏览(34)
  • MySQL的事务隔离级别

    目录 事务隔离级别的概念 脏读(Dirty Read): 不可重复读(Non-Repeatable Read): 幻读(Phantom Read): 读未提交(Read Uncommitted) 读未提交隔离级别的特点 示例 优势和劣势 读已提交(Read Committed) 读已提交隔离级别的特点 示例 优势和劣势 可重复读(Repeatable Read) 可重复读隔

    2024年02月09日
    浏览(40)
  • Spring的事务隔离级别

    Spring的事务隔离级别是用于控制事务并发访问数据库时的行为。Spring框架提供了五个事务隔离级别,分别是: 1. DEFAULT(默认):使用数据库默认的事务隔离级别。在大多数情况下,这等同于使用READ_COMMITTED级别。 2. READ_UNCOMMITTED(读取未提交数据):最低的隔离级别,允许一

    2024年02月09日
    浏览(33)
  • 数据库事务隔离级别

    读未提交、读已提交、可重复读、串行化 对事务中所有读写的数据加上读锁、写锁、范围锁。所以冲突的事务必须同步执行。 核心是只对事务中所有读写的数据加上读锁、写锁,不加范围锁。 相比于读已提交,由于对整个事务都加上了读锁,避免其他事务可以进行更新,进

    2024年02月08日
    浏览(31)
  • 详解MySQL事务隔离级别

    一个事务具有 ACID 特性,也就是(Atomicity、Consistency、Isolation、Durability,即 原子性 、 一致性 、 隔离性 、 持久性 ),本文主要讲解一下其中的 Isolation ,也就是事务的 隔离性 。 概述 四种隔离级别分别是: 读未提交(Read Uncommitted) :最低的隔离级别,事务对数据的修改即使

    2024年02月09日
    浏览(35)
  • 探索MySQL隔离级别

    数据库事务的隔离级别是一个重要的概念,它定义了一个事务可能受其他并发事务影响的程度。MySQL提供了四种标准的隔离级别,每个级别都以不同的方式平衡了一致性和性能。本文将详细介绍这些隔离级别,并提供相应的示例。 概念: 这是最低的隔离级别,允许一个事务可

    2024年02月02日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包