乐趣区

关于java:面试官的灵魂一击你懂-MySQL-事务日志吗

写作背景

大家都分明,日志是 MySQL 数据库的重要组成部分,记录着数据库运行期间各种状态信息。MySQL 日志次要包含 「谬误日志」「查问日志」「慢查问日志」「二进制日志(binlog)」 和 事务日志(redo log、undo log) 几大类。

其中,「二进制日记」「事务日记」 尤为重要,始终被人器重、深入研究;可是事实很仁慈,器重或者说大多数人个别都是理解个外表,真正懂得人并不多。真想攻破这两块日记必须下血本,而且还不肯定能攻破。然而不要紧,为了让你们省下血本还能顺利攻破这两块日记,我间断钻研几周 MySQL 日记,最终肝出了这篇文章。

必要概念字典介绍

根底不牢地动山摇,还是惯例套路,先把必要常识遍及 / 复习一遍,当后续文章呈现疑虑反过来看下这些概念字典,说不定能柳暗花明又一村呢?

写了又写,想了又想,纠结了良久,这部分常识的确有点多,最初还是决定将这些必要概念字典独自分出一个文章,后续打算用截图形式引入各个章节中,倡议遇到不懂名词查阅一下字典。

意识二进制日记(Binlog)

Binlog 概念

Binlog 是逻辑日记,用于记录数据库执行的写入操作(查问不记录)信息,Server 层记录和引擎层无关,并且是以追加形式进行写入,能够通过参数 max_binlog_size 设置每个 Binlog 文件的大小,文件大小达到设定值时会生成新的文件来保留日记。

Binlog 作用

在理论利用中,次要用在两个场景:主从复制和数据恢复

  • 主从复制场景:在 Master 主端开启 Binlog,将 Binlog 产生到各个 Slave 从端,Slave 从端重放 Binlog 从而达到主从数据统一
  • 数据恢复场景:通过应用 mysqlbinlog 工具来复原数据

Binlog 记录过程及刷盘机会

Binlog 大抵记录过程是先写 Binlog Buffer,而后通过刷盘机会,管制刷入 OS Buffer,管制 fsync() 进行写入 Binlog File 日记磁盘的过程。

对于 Binlog,MySQL 是通过参数 sync_binlog 参数来管制刷盘机会,取值是 0、1 和 N 三种值。0 示意由零碎自行判断何时调用 sync() 写入磁盘;1 示意每次事务 commit 都要调用 fsync() 写入磁盘;N 示意每 N 个事务,才会调用 fsync() 写入磁盘。

Binlog 记录格局

MySQL 5.7.7 版本之前默认格局是 STATEMENT,版本之后默认是 ROW,能够通过参数 binlog-format 指定。

意识事务日记(Undo log)

Undo log 概念

Undo log 是 逻辑日记、回滚日记。比方一条批改 +3 的逻辑语句,Undo log 会记录对应一条 -3 的逻辑日记,一条插入语句则会记录一条删除语句,这样产生谬误时,依据执行 Undo log 就能够回滚到事务之前的数据状态。

Undo log 作用

  • 回滚数据:当程序产生异样谬误时等,依据执行 Undo log 就能够回滚到事务之前的数据状态,保障原子性,要么胜利要么失败。
  • MVCC 一致性视图:通过 Undo log 找到对应的数据版本号,是保障 MVCC 视图的一致性的必要条件。

Undo log 记录过程及刷盘机会

刷盘过程及机会相似于 Binlog 和 Redo,能够参考 Redo log 刷盘机会章节给出的图片,曾经体现进去了。

Undo log 总结

Undo log 日记内容不是很多,重点是回滚和多版本控制 MVCC 那块。此外,我记得印象笔记粗浅的是长事务会导致日记过多,这个日记就是 Undo log。因为长事务存在,导致须要保留很多视图快照,其实这里就是波及到 Undo log 何时删除和生成的问题,过后纠结良久,其实很简略。生成是事务开始后写 Redo log 之前生成,当没有事务须要用到 Undo log 时就会被删除。举个例子,如果事务 A 始终存活,那么事务 A 之后产生的事务 B、C… 等等就算提交了,也不会被删除,因为事务 A 须要用到 B、C… 事务去找 A 的版本。所以防止长事务能够缩小 Undo log 日记量,当然还能够进步性能。

意识事务日记(Redo log)

Redo log 概念

Redo log 是重做日记,属于 InnoDB 引擎的日记。是物理日记,日记记录的内容的是数据 <typo id=”typo-1882″ data-origin=” 页 ” ignoretag=”true”> 页 </typo> 的更改,这个页“做了什么改变”。如:add xx 记录 to Page1,向数据页 Page1 减少一个记录。

Redo log 作用

  • 前滚操作:具备 crash-safe 能力,提供断电重启时解决事务失落数据问题。
  • 进步性能:先写 Redo log 记录更新。当等到有闲暇线程、内存不足、Redo log 满了时刷脏。写 Redo log 是程序写入,刷脏是随机写,节俭的是随机写磁盘的 IO 耗费(转成程序写),所以性能失去晋升。此技术称为 WAL 技术:Write-Ahead Logging,它的关键点就是先写日记磁盘,再写数据磁盘。

Redo log 两阶段提交

更新内存后引擎 <typo id=”typo-2178″ data-origin=” 层 ” ignoretag=”true”> 层 </typo> 写 Redo log 将状态改成 prepare 为预提交第一阶段,Server 层写 Binlog,将状态改成 commit 为提交第二阶段。两阶段提交能够确保 Binlog 和 Redo log 数据一致性。

Redo log 容灾复原过程

MySQL 的处理过程如下

  • 判断 redo log 是否残缺,如果判断是残缺(commit)的,间接用 Redo log 复原
  • 如果 redo log 只是预提交 prepare 但不是 commit 状态,这个时候就会去判断 binlog 是否残缺,如果残缺就提交 Redo log,用 Redo log 复原,不残缺就回滚事务,抛弃数据。

只有在 redo log 状态为 prepare 时,才会去查看 binlog 是否存在,否则只校验 redo log 是否是 commit 就能够啦。怎么查看 binlog:一个残缺事务 binlog 结尾有固定的格局。

Redo log 刷盘机会

Undo log 的刷盘机会和 Redo log 差不多,然而对于 Undo log 我没找到对应的刷盘参数设计,所以 <typo id=”typo-2666″ data-origin=” 不在 ” ignoretag=”true”> 不在 </typo> 提。Redo log 每次先写入 Redo Log Buffer 中,而后通过刷盘机会管制刷入 OS Buffer 工夫和刷入日记磁盘的工夫。

在 Undo Log 中,MySQL 是通过参数 innodb_flush_log_at_trx_commit 来管制刷盘机会,取值是 0、1 和 2 三种值。0 示意事务提交后,每秒写入 OS Buffer 并调用 fsync() 写入日记磁盘中;1 示意每次事务提交会写入 OS Buffer 并调用 fsync() 将日记写入日记磁盘中。2 示意事务每次提交写入到 OS Buffer,每秒调用 fsync() 写入日记磁盘。可见参数为 1 是最平安的,同时也是默认值。

Redo log 存储形式

上图是日记磁盘的 Redo log 环形设计图(从头写,写到完结又从头开始写~ 循环)。write pos 和 check point 是两个指针,write pos 指针指向以后日记文件写入的地位,check point 指针指向以后要擦除的开始地位。图中绿色局部是能够写入 Redo log 中央,每次写入,write pos 指针会顺时针推动,当然根本不会与 check point 指针重合,因为 MySQL 有这种机制去实现,每次触发检查点 checkpoint,check point 会指针向前推动,这个过程就是须要进行刷日记和数据磁盘,记录相应的 LSN,引出难点 LSN。

Redo Log 检查点

啥时候会触发检查点 checkpoint

「Checkpoint 产生的工夫、条件及脏页的抉择等都非常复杂。而 Checkpoint 所做的事件无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么工夫触发 Checkpoint。这些本文不会去钻研」

Redo Log LSN

LSN 这个概念,比较复杂。LSN 称为日志的逻辑序列号(log sequence number),在 innodb 存储引擎中,lsn 占用 8 个字节。LSN 的值会随着日志的写入而逐步增大。「能够简略了解 SLN 就是记录从开始到当初曾经产生了多少字节的 Redo log 值」

存储形式两个指针又是通过 LSN 计算失去指向地位,因为 LSN 记录的是文件的大小字节,当超过文件大小时,须要用取模计算出这两个指针地位,取模使得写入就会从头开始写,这样使得两个指针在一个文件中,始终落在循环地位,你追我赶的过程。这就是 Redo log 环形逻辑思维设计实现。

下面提到 LSN 比较复杂,是因为它有很多个值,输出命令 show engine innodb status;,能够看到四个的 lsn 记录

为了不便辨认,我都为它们重新命名,如下所示。名词记不住,前面无奈持续深刻

  • 内存日记:redo log buffer lsn;磁盘日记:redo log file lsn;

个别关系为:redo log buffer lsn >= redo log file lsn,如果刷盘机会为 1,则 redo log buffer lsn = redo log file lsn。

  • 内存数据页:data buffer lsn;数据磁盘数据页:data disk lsn;

个别关系为 data buffer lsn > data disk lsn,如果曾经刷入数据磁盘,则 data buffer lsn = data disk lsn。

  • 检查点:chckpoint lsn;

前面提到检查点刷盘,数据刷盘和日记刷盘(如果有日记刷盘:则阐明我假如的日记刷盘的机会设置值不为 1,为 1 是同步的,即始终 redo log buffer lsn = redo log file lsn,不会由检查点触发刷日记磁盘)。

都说 Redo log 是环形记录,那么怎么记录的?上面联合 LSN 给出记录过程虚构图,能够比照 Redo log 存储形式图

相干常识:日记磁盘 + redo log file lsn + checkpoint lsn + 双指针(write pos、check point)

1-8 按工夫程序产生。1 点是假如最后的状态;2、3 点写日记磁盘;4 点是触发了检查点 checkpoint,进行刷盘,「checkpoint lsn= 1 开始」,刷盘完结并更新「checkpoint lsn=512」。在 5 点、6 点曾经刷过了一循环内存、二循环内存,「从头开始写入 log,两个指针指向回到了头部」。第 7 点也是一个触发 checkpoint 的过程。9 点是假如没有更新,最初达到均衡的后果,即内存中数据页和日记都实现了刷盘。

整个流程:

在某些状况下,触发 checkpoint,触发数据页和日志页刷盘,此时将内存中的脏数据 — 数据脏页和日志脏数据 ” 别离刷到数据磁盘和日记磁盘中,而且两者刷盘速度不一样。checkpoint 会爱护机制,当数据刷盘速度超过日志刷盘时,将会临时进行数据刷盘,期待日志刷盘进度超过数据刷盘。

刷盘时,对于数据磁盘,全部都是在内存中,此时每次刷一个数据页到内存更新数据页也更新了 「data disk lsn」 为 「data buffer lsn」(在更新内存数据页时,会更新 data buffer lsn)「。」

对于日记磁盘,除了要记录 checkpoint lsn 的值为检查点 checkpoint 的值(必须在完结时间接记录一个值,速度很快),这里是针对日记刷盘机会不是 1(1 是同步缓存刷日记刷盘)时,并且日记还没刷到日记磁盘须要触发将缓存中日记提前刷到日记磁盘中,此时会将 redo buffer log 刷到 redo log file 中也更新了 redo log file lsn 为 redo log buffer lsn。

模仿检查点触发前后,整个流程变动,一个数据页和日记,「数据变动及 lsn 从 179-180 的变动图(刷盘机会不为 1)」

Redo log 容灾复原过程与 LSN

Redo log 容灾复原过程和 LSN 的常识,再次细化 Redo log 复原过程

重启 innodb 时,Redo log 完不残缺,采纳 Redo log 相干常识。用 Redo log 复原,启动数据库时,InnoDB 会扫描数据磁盘的数据页 data disk lsn 和日志磁盘中的 checkpoint lsn。两者相等则从 checkpoint lsn 点开始复原,复原过程是利用 redo log 到 buffer pool,直到 checkpoint lsn 等于 redo log file lsn,则复原实现。

如果 checkpoint lsn 小于 data disk lsn,阐明在检查点触发后还没完结刷盘时数据库宕机了。因为 checkpoint lsn 最新值是在数据刷盘完结后才记录的,检查点之后有一部分数据曾经刷入数据磁盘,这个时候数据磁盘曾经写入局部的局部复原将不会重做,间接跳到没有复原的 lsn 值开始复原。

理解 ChangeBuffer

为啥提到 ChangeBuffer

为啥本文我会提到 ChangeBuffer 呢,其实很多时候会将 ChangeBuffer 和 Redo log 搞混,两者都是巧用内存,缩小磁盘 IO,为了不弄混我感觉有必要专门对这个进行一个解说。

ChangeBuffer 概念及作用

上面是我对 ChangeBuffer 的简略介绍

也就是说对于更新的操作,如果用到了 ChangeBuffer,更新的数据所在的数据页如果不在内存中,将不必去数据磁盘将数据页读到内存,而是将这一次操作记录在 ChangeBuffer 中,「ChangeBuffer 次要节俭的则是随机读磁盘的 IO 耗费」,下次读取查问等读取数据页时用上 ChangeBuffer 中的记录即可。其实也是一种巧用内存的思维。

ChangeBuffer 与 Redo log 区别

Redo log 次要节俭的是随机写磁盘的 IO 耗费(转成程序写),而 ChangeBuffer 次要节俭的则是随机读磁盘的 IO 耗费

这句话怎么了解,看上面:

Redo log 与 ChangeBuffer (含磁盘长久化) 这 2 个机制,不同之处在于优化了整个变更流程的不同阶段。

先不思考 Redo log、ChangeBuffer 机制,简化形象一个更新 (insert、update、delete) 流程:

  • 从磁盘读取待变更的行所在的数据页,读入内存页中
  • 对内存页中的行,执行变更操作
  • 将变更后的数据页,写入至数据磁盘中

其中,流程中的步骤 1 波及随机读磁盘 IO;步骤 3 波及随机写磁盘 IO;刚好对应 ChangeBuffer 和 Redo log。

对那句话的了解答案:

  • ChangeBuffer 机制,优化了步骤 1——防止了随机读磁盘 IO,将不在内存中的数据页的操作写入 ChangeBuffer 中,而不是将数据页从磁盘读入内存页中
  • Redo log 机制,优化了步骤 3——防止了随机写磁盘 IO,将随机写磁盘,优化为了程序写磁盘(写 Redo log,确保 crash-safe)

有没有用到 ChangeBuffer 对于 Redo log 的区别

Redo log 机制,为了保障 crash-safe,始终都会用到。有无用到 ChangeBuffer 机制,对于 redo log 这步的区别在于—— 用到了 ChangeBuffer 机制时,在 Redo log 中记录的本次变更,是记录 new change buffer item 相干的信息,而不是间接的记录物理页的变更。在咱们 mysql innodb 中,ChangeBuffer 机制不是始终会被利用到,仅当待操作的数据页以后不在内存中,须要先读磁盘加载数据页时,ChangeBuffer 才有用武之地。

ChangeBuffer 的 merge 过程

除了拜访这个数据页会触发 merge 外,零碎有后盾线程会定期 merge。在数据库失常敞开(shutdown)的过程中,也会执行 merge 操作。

merge 过程做三步

  • 从磁盘读入数据页到内存(老版本的数据页);
  • 从 change buffer 里找出这个数据页的 change buffer 记录 (可能有多个),顺次利用,失去新版数据页;
  • 写 redo log。这个 redo log 蕴含了数据的变更和 change buffer 的变更。

日记大连贯 U -R-B,一举攻破拿下

后面别离讲的是 Binlog、Undo log 和 Redo log,上面将他们都串联起来,在一些流程体现全副日记。

同样,以一些最经典的更新语句例子开展阐明。

制作演示数据

测试语句:插入语句 + 查问语句,a 字段是一般索引

<pre language=”javascript” code_block=”true”>1、insert into ta(a,b) values(2,5),(7, 5)

2、select * from t where a in (2, 7)
</pre>

假如原来的数据如下图,数据页 page1 在内存中,page2 不在。插入的数据 (2,5) 落在 page1,数据 (7,5) 落在 page2 中。

假如没有日记和 ChangeBuffer 示范

先不思考所有日记及 ChangeBuffer 机制,简化形象一个更新 insert 流程

  • 从磁盘读取待变更的行所在的数据页,读入内存页中
  • 对内存页中的行,执行变更操作
  • 将变更后的数据页,写入至数据磁盘中

思考所有日记和 ChangeBuffer 示范 – 现有 Innodb 流程

过程是 两阶段提交 —– 日记刷盘 —— 数据刷盘(波及 Redo log lsn 和 ChangeBuffer 的内容)

两阶段提交过程

  • 数据 (2,5) <typo id=”typo-7683″ data-origin=” 所在页 ” ignoretag=”true”> 所在页 </typo> page 1 在内存中间接更新内存;数据 (7,5) <typo id=”typo-7714″ data-origin=” 所在页 ” ignoretag=”true”> 所在页 </typo> page 2 不在内存中,记录 change buffer(具备唯一性的索引或者没有应用 change buffer 的操作是将磁盘中的数据页读入内存中并做更新)。
  • 写 undo 日记。「先写缓存,前面依据刷盘参数决定何时刷入磁盘,前面的 redo/Binlog 都一样」。日记刷盘 在每一个日记中根本曾经提到,它和设置的参数无关,下文不会再开展介绍。
  • 写 redo 日记(先记在内存中的更新,而后 <typo id=”typo-7919″ data-origin=” 记不 ” ignoretag=”true”> 记不 </typo> 在内存中的 change buffer 的扭转)
  • 日记状态改成 prepare 阶段。
  • 写 Binlog 日记。
  • 提交事务,日记状态改成 commit 阶段。

merge 过程

紧接着上文,图片可高低参考,假如当初执行查问语句 select * from t where a in (2, 7),此次查问索引 a=7 所在的数据页不在内存中,并且上一步更新曾经在 change buffer 中有记录,将会触发 merge 过程

  • 将 page 2 读入内存
  • 顺次利用 change buffer 中的记录,失去最新版数据页
  • 写入 redo,之前记录的 changebuffer 改变,当初改成数据页的改变

至于 changebuffer 被利用后是删除还是标记,还有 redo 中原有的记录 changebuffer 的改变怎么调整是删除还是批改成数据页的改变这里上面的图是依照本人的想法形容进去,如有误望留言斧正。

[图片上传失败 …(image-e98fa5-1603353858527)]

数据刷盘过程

数据刷盘 flush 的有四种状况

  • InnoDB 的 redo log 写满了。这时候零碎会进行所有更新操作,把 checkpoint 往前推动,redo log 留出空间能够持续写
  • 零碎内存不足。当须要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页应用。如果淘汰的是 脏页,就要先将脏页写到磁盘
  • MySQL 认为零碎闲暇的时候
  • MySQL 失常敞开的状况

数据刷盘也代表着 Redo log 检查点 checkpoint 触发,较为简单。

假如数据刷盘 flush 的四种状况产生了一种,那么分割上文的过程将如下

  • 将 <typo id=”typo-8635″ data-origin=” 脏页 ” ignoretag=”true”> 脏页 </typo> 从内存中刷回到数据磁盘
  • 刷完后更新检查点 checkpoint 的值

[图片上传失败 …(image-c2d38-1603353858527)]

流程两头某个环节数据库宕机后,复原具体过程,这些留在心里了,没往下来写,读者能够自行思考,不难。

结尾

整个文章讲了 Binlog、Undo log 和 Redo log,随带一提 ChangeBuffer,对此,讲到这里,基本上要把我要讲的曾经讲完,内容挺多,有急躁能够缓缓啃!

思考环节,上面留下两个问题,欢送大家留言解答

1、为啥 Binlog 没有 crash-safe 性能?

2、保障 crash-safe 为啥要用两个日记,不能用一个日记吗(Redo log 或 Binglog)?

写在最初

欢送关注公众号:【Java 斗帝】

1、欢送大家关注,继续推出干货~
2、后盾回复 666,支付私人整顿的 1000 道互联网面试题

退出移动版