一文带你看懂 InnoDB 中的 MVCC、Undo、Redo 机制


写在开头

  接上一篇博客:InnoDB 事务与锁的前世今生,本文是在 InnoDB 并发事务与锁内容之后,再来分析 InnoDB 引擎针对并发事务数据一致性操作的详细介绍。

  本文将从:1.什么是 MVCC?  2.MVCC 在 MySQL 中的体现   3.Undo Log   4.Redo Log 这些方面来详细的介绍。通过案例、插图,以最通俗易懂的方式,让你彻底掌握 MVCC 的来龙去脉。

Tips:本文以 MySQL默认事务隔离级别 REPEATABLE READ(可重复读)来介绍。

1.一题引出MVCC

  上一篇博客中,我们已经详细介绍了共享锁排它锁意向锁自增锁临键锁间隙锁记录锁这些内容。我们就接着这些锁来分析下面这两个示例。
在这里插入图片描述
  以上两个示例,最终返回查询结果均是10。疑惑的是,已经+X锁了,为什么还是能够查询成功呢?在此处就引出了 MVCC这个概念。虽然这两种情况看着查询结果一致,但是 InnoDB 底层实现却是完全不同的,它们底层的实现与 MVCC 又有什么关系呢?接下来就带你进入 MVCC 的世界,领略 InnoDB 引擎的牛X之处。

2.什么是 MVCC

  MVCC,全称 Multi-Version Concurrency Control(多版本并发控制)。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。(本段摘自百度百科:MVCC)

  简单理解为:并发访问数据库时(读和写),对正在事务内处理的数据做多版本的管理,以达到用来 避免写操作的阻塞,从而提升读操作的并发问题 (基于undo log 快照读来解决)

  (可能还不是很懂,继续往下看,案例图解带你继续分析↓↓↓)

3.MVCC 在 MySQL 中的体现

  我们在数据库创建表时,比如有idnameage三个字段,但是 InnoDB 的内部实现中为每一行数据增加了三个隐藏列用于实现 MVCC。

列名长度(字节)作用
DB_TRX_ID6插入或更新行的最后一个事务的事务标识符(版本号)。(删除视为更新,将其标记为已删除)
DB_ROLL_PTR7写入回滚段的撤消日志事务标识符(版本号)(若行已更新,则撤消日志记录包含在更新行之前重建行内容所需的信息)
DB_ROW_ID6行标识(自增id,也就是当我们建表没有指定主键列时,mysql会自动生成DB_ROW_ID用作主键)

所以,我们创建表的真正结构,其实是下面这个样子的:

idnameageDB_ROW_IDDB_TRX_IDDB_ROLL_PTR

  InnoDB引擎,通过为每一行记录添加两个额外的隐藏的值( DB_TRX_IDDB_ROLL_PTR )来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。并不会存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。下文中,我们就只拿这两个字段+事务版本号来做介绍了。

3.1 插入流程

  MVCC 在 MySQL 执行插入操作时,InnoDB 会为这个新的数据行,DB_TRX_ID 字段记录当前的事务版本号。执行流程图如下所示:
在这里插入图片描述

3.2 删除流程

  MVCC 在 MySQL 执行删除操作时,InnoDB 会为要删除的这个行,DB_ROLL_PTR 字段记录当前删除(回滚)的事务版本号。执行流程图如下所示:
在这里插入图片描述

3.3 修改流程

  MVCC 在 MySQL 执行修改操作时,InnoDB 会写一个这行数据的新拷贝,这个拷贝的版本号为当前的事务版本号(DB_TRX_ID 字段);同时它会将这个事务版本号写到旧行的删除(回滚)版本号中( DB_ROLL_PTR 字段)。执行流程图如下所示:
在这里插入图片描述

3.4 查询流程

MVCC 在 MySQL 执行查询操作时,InnoDB 查询数据有两条规则:

  1. 查找 数据行事务版本号 <= 当前事务ID 的数据行;
  2. 查找 删除(回滚)版本号为 NULL 或者 删除(回滚)版本号大于当前事务ID 的数据行。

执行流程图如下所示:
在这里插入图片描述

4. 使用 MVCC 后对示例分析

  有两个事务1、2 对数据进行操作。①②③④为执行步骤序号
在这里插入图片描述

勘正提示:
  事务2中update 语句有误,应该为 update user set age = 25 where id = 1

4.1 情形一

  按照步骤 ①②③④②执行操作。流程图如下所示:在这里插入图片描述

4.2 情形二

  按照步骤 ③④①②执行操作。流程图如下所示:在这里插入图片描述

4.3 情形分析

  我们发现,两个事务A、B 在两种情形下执行相同的操作,但是查询却出现了不同的结果。那么这问题出在了哪里?我们继续向下分析。其实这问题并不是出在 MVCC 上,针对这种情况,InnoDB 引擎又是怎么解决这一问题的呢??这就引出了:undo Log、redo Log 这两个概念。

5. Undo Log

  在 Undo Log 之前,我们遇到两个问题加 X 锁写操作阻塞时,那么数据库在高并发时肯定时不行的; 使用 MVCC 控制版本时,又发现一个重复读导致的数据不一致问题。此时:InnoDB 引擎就是通过引入 undo log ,通过读快照的方式来解决这两个问题的。

  undo log 的出现,既可以主要解决数据原子性问题的同时,又可以配合 MVCC 间接解决高并发下的读操作阻塞问题。

5.1 什么是 undo log

  undo log 是指在事务开始之前、操作数据之前,首先将需要操作的数据备份到一个地方的过程。undo log 的出现,是为了 实现事务的原子性 而出现的产物。

5.2 undo 如何实现事务原子性

   事务在处理的过程中,如果出现了错误用户执行了 rollback。MySQL 可以利用 undo log 中的备份,将数据恢复到事务开始之前的状态。

  在 InnoDB 引擎中,undo log 和 MVCC 一起来实现 MySQL 数据的多版本控制。 在事务提交之前,undo 保存了未提交事务之前的版本数据到 undo log,undo log 中的数据可以作为数据旧版本快照来供其他并发事务进行快照读。

5.3 快照读、当前读

  基于 undo log,可以将 undo log 中的数据作为旧版本快照的方式,来供其他并发事务进行快照读。(这就是 MVCC 配合 undo log 来解决读操作的并发问题)

Ⅰ.快照读 (通过MVCC + undo log 解决幻读问题)

  SQL 读取的数据是快照版本,也就是历史版本。普通的 Select 就是基于快照读的方式 。InnoDB 快照读,数据的读取将由 cache(原本数据) + undo(事务修改过的数据)两部分组成。

Ⅱ.当前读 (通过Next-key lock 解决幻读问题)

  SQL 读取的数据是最新版本。通过 锁机制 来保证读取的数据无法通过其他事务进行修改。常用的 updatedeleteinsertselect xxx LOCK IN SHARE MODE(共享锁)select xxx FOR UPDATE(排他锁) 都是当前读。

5.4 情形二基于 undo log 再做分析

了解了 undo log 之后,我们 基于 undo log + MVCC 再来按步骤 ③④①② 分析执行结果,图示如下:
在这里插入图片描述
执行流程:

  1. 执行第④步,update user set age = 18 where id = 1; 会将 user 表中数据备份到 undo buffer 缓存;
  2. update 后,如果事务2执行 rollback 操作,那么 user 表会基于 undo buffer 数据回滚到 update 之前状态;(这就是 undo 为了解决事务原子性而出现的产物)
  3. 既然已经将旧数据进行备份,InnoDB 引擎就将 undo buffer 当做一个快照表来进行读取;
  4. 执行 select age from user where id = 1; 查询时。便会从快照表中读取数据;(这就是为什么 update 加了 X 锁,还能读取数据的原因)
  5. undo buffer 是在 cache 缓存中,最终它里面的数据还是会 flush 到 disk 磁盘上的。(undo buffer 是一个缓冲区,是有一定的大小的,如果达到指定大小,事务还未commit/rollback,此时就会写磁盘了)

5.5 硬盘中的 undo log 文件有什么用

  如果当前事务在执行过程中, MySQL 服务崩溃了,重启时则会从 undo log 日志中恢复;此时如果断电了,重启后还是会根据 undo 日志进行恢复。此操作具有幂等性,重复多少次都没有问题

6. Redo Log

6.1 什么是 redo log

  redo log 是指事务中操作的任何数据,将最新的数据备份到一个地方的过程。redo log是为了实现事务的持久性而出现的产物。 redo log 的持久性,并不是随着事务的提交才写入,而是在事务的执行过程中,便开始写入到 redo buffer 中,最后以 redo log 文件方式 flush 到磁盘中。具体的落盘策略我们可以手动配置。

6.2 redo 如何实现事务持久性

  redo log 的出现,就是防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。

6.3 redo 日志如何保存

  引入 redo log 机制,InnoDB引擎认为数据只要写入到 redo log 成功,就认为当前事务已经完成,而不是要求你将数据全部 flush 到磁盘(*.IBD 文件)才算事务完成。如果每次修改都 flush 数据到磁盘,那显然是很消耗时间的;但是如果你将日志写入到 redo log中,根据 redo log 落盘策略对数据进行保存,显然效率会高很多(即:将耗时的操作放到后台去操作)。
在这里插入图片描述
  如果此时 MySQL 服务异常、断电等情况,在重启时 MySQL 便会读取 redo log 中的数据来进行恢复。此时你或许会有这样一个疑惑:redo buffer 中的数据还没有来得及写入到 redo log 这部分的数据,不会丢吗?此处就涉及到了 redo buffer 数据 落盘到 redo log 的策略问题。如下介绍

6.4 redo log 参数配置(不建议修改,推荐使用默认)

  Redo log 日志,记录在{datadir}/ib_logfile0、ib_logfile1 这两个文件。我们可以进入 MySQL 所在数据目录下查看。mysql 数据目录在哪个位置,通过命令:show variables like 'datadir';查看。我们也可以通过命令:show variables like 'innodb_log_group_home_dir'来指定 redo log 存储位置(不建议修改)
在这里插入图片描述
  一旦事务成功提交且数据持久化落盘之后,此时Redo log中的对应事务数据记录就失去了意义,所以 Redo log 日志文件的写入采用的是循环写入的方式。

我们也可以根据情况对 redo log 相关参数做修改,但是还是推荐你使用默认配置就好:

  1. 指定Redo log 日志文件组中的数量 innodb_log_files_in_group,默认为2。 (即:ib_logfile0 和 ib_logfile1)
  2. 指定Redo log 每一个日志文件最大存储量 innodb_log_file_size,默认48M。 (上图中:50331648 就是 48M)
  3. 指定Redo log 在 cache/buffer 中的 buffer 池大小 innodb_log_buffer_size ,默认16M。 (即上图中蓝色 cache/buffer 区域大小)

6.5 数据落磁盘策略

  Redo buffer 持久化 Redo log 的策略,我们可以使用命令:show variables like 'Innodb_flush_log_at_trx_commit';查看。如下图所示:默认为1。
在这里插入图片描述
共有 3 个值可选:

  1. 取值 0。即:每一秒提交 redo bufferredo log os cacheflush cache to disk(如果突然断电,最多丢失一秒的事务数据);
  2. 取值 1。(默认值)即:每次事务提交执行 redo bufferredo log os cacheflush cache to disk(最安全,但是性能最差);
  3. 取值 2。每次事务提交执行 redo bufferredo log os cache,再每一秒执行 flush cache to disk操作(此处分两种情况:如果 MySQL 异常,此时已经保存到 redo log os cache,所以数据也是不会丢失的;每一秒执行 flush cache to disk,此时如果操作系统崩了,最多损失也是一秒的事务数据)

  在这三个策略中,MySQL 作为一个 RDBMS,安全性一定是它优先考虑的,所以默认选择的落盘策略为 1。如果你要手工修改,在 0 和 2 之间,推荐使用 2。(此处也不推荐你来修改它,毕竟数据安全第一)


至此,InnoDB 引擎中的 MVCC 、Undo、Redo 机制就此介绍完毕。


MySQL 同系列文章,请参考:

  1. 了解MySQL体系结构
  2. 一文带你看懂 MySQL 存储引擎
  3. 还不了解 MyISAM 和 InnoDB 的区别?看这里就够了
  4. MySQL为什么没有走索引?是这些原因在搞鬼
  5. 一条SQL语句的坎坷之旅(MySQL底层执行流程分析)
  6. 不会MySQL调优?来来瞅瞅SQL的执行计划吧
  7. InnoDB 事务与锁的前世今生
  8. 一文带你了解 InnoDB 中的 MVCC、Undo、Redo 机制

博主写作不易,加个关注呗

求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙

博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

扛麻袋的少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值