数据库

Mysql为什么要用两个日志模块?为什么InnoDB采用二段式提

2021-02-24  本文已影响0人  i_cyy

Mysql为什么要用两个日志模块?为什么采用二段式提交?

首先回答第一个问题,得了解Mysql的日志模块,再开始讲日志模块,先回顾一下Mysql查询一条语句的一个执行过程:

Mysql语句执行流程

Mysql主要有两个日志模块:redo logbin log,其中redo log就是重做日志,

redo log(InnoDB特有的日志,发生在引擎层)

redo log是物理日志,记录了这个数据页 “做了什么改动”,发生在InnoDB引擎中,当修改数据时,InnoDB将数据写入内存,在写入redo log就算修改完了,而且redo log是顺序写磁盘速度很快。避免了修改数据时直接修改数据行带来的大量随机IO,采用了随机IO转顺序IO的策略。

redo log顺序写实际上是循环写固定几个文件,写满一轮就要从头开始覆盖。它包括两个位点,check point和write pos,write pos是写到那个位置了,循环往后递增,check point是当前要擦除的位置。二者中间的空间是可写入的,当write pos追上check point时,就会先停下更新,覆盖掉一些记录,然后继续写入redo log。

redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。

binlog(归档日志,Server层日志)

binlog是逻辑日志,记录了“id=2的记录的c加上1”,用于归档,它又是什么用呢?顾名思义它的主要作用就是归档(还有主从)!有三种模式:

binlog没有固定大小,每次都是追加记录不会覆盖之前的。

在server层,每个存储引擎都可以使用.

最开始mysql没有InnoDB,也就没有redo log,也就没有crash-safe能力,binlog只能用于归档,当InnoDB以插件的形式加入mysql后,需要crash-safe能力,所以就带来了redo log

二者很重要的不同:

  • redo log是物理日志,记录的是哪个数据页做了什么修改;binlog是逻辑日志,记录的是语句的原始逻辑,比如“id=5的c字段加1”
  • redo log是循环写的,binlog是可以追加写入的,写到一定大小会写下一个,不会覆盖以前的日志

sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

综上,这个地方为什么要有两份日志,一个原始是当时MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用来归档,而InnoDB是第三方公司以插件的形式引入MySQL的,因为只依靠binlog是没有crash-safe的能力,所以要使用InnoDB的日志系统redo log。

两种日志主要的不同:

  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
  1. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”
  1. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

再谈谈为什么需要采用二段式提交,再解答这个问题之前,我们先来看看一条更新语句的执行流程:

image

过程文字描述:

  1. 客户端发来update t set c = c + 1 where id = 2的update语句
  1. 经过前面的分析优化流程后,执行器调用存储引擎拿到id=2的记录
  1. 拿到后进行运算字段c得到新数据行
  1. 调用执行器将数据行写入
  1. 存储引擎将数据行更新到内存中,并写redo log,并且redo log的状态为prepare,返回到执行器
  1. 执行器根据语句生成binlog,并写入磁盘
  1. 执行器调用提交事务接口,将刚刚的redo log状态设置为commit,更新就完成了

可以看到这里redo log的prepare -> commit是二阶段提交事务,现在就可以回答为什么使用两阶段提交了。

为什么要使用二阶段提交呢?

redo logbinlog都可以保证事务的提交状态,二阶段提交可以使这两个状态在逻辑上保持一致。

两阶段提交在mysql中:

  1. 执行器调用存储引擎更新数据行

  2. 存储引擎将数据记入内存中,并写redo log,并将redo log状态设置为prepare,并返回

  3. 执行器生成binlog,并写入磁盘

  4. 执行器调用存储引擎的提交事务接口

  5. 存储引擎将刚刚的redo log状态设置为commit

当在以下阶段发生crash,然后用binlog进行主备库同步:

注:崩溃恢复的判断规则

  1. 如果redo log里面的事务是完整的,也就是有了commit标识,则提交事务

  2. 如果redo log里面的事务的prepare是完整的,但没有commit,则判断binlog里是否存在并完整

    1. 如果是,则提交事务

    2. 如果否,则回滚事务

这里的判断binlog是否完整是通过“binlog是有完整格式”做到的,完整格式包括:

并且在5.6.2版本后增加了binlog-checksum参数

这里redo log和binlog是通过叫一个xid的共同的数据字段做到的,在崩溃恢复的时候,会按顺序扫描 redo log:

正常运行的实例,最终落盘的修改数据是从redo log过去的还是从buffer pool过去的?

redo log内并没有记录完整的数据页内容,所以它不具有更新完整数据页的能力,所以最终落盘的数据不是从redo log过去的。

  1. 如果是正常运行的实例,数据页被修改后,与磁盘的数据页不一致,成为脏页。最终落盘的数据就是将内存中的数据页落盘。

  2. 在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。

参考:

https://blog.csdn.net/qq_35362055/article/details/105707788

https://blog.csdn.net/weixin_42195978/article/details/113635093

上一篇下一篇

猜你喜欢

热点阅读