美文网首页数据库
Mysql为什么要用两个日志模块?为什么InnoDB采用二段式提

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

作者: i_cyy | 来源:发表于2021-02-24 16:28 被阅读0次

    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”,用于归档,它又是什么用呢?顾名思义它的主要作用就是归档(还有主从)!有三种模式:

    • Statement: 记录每一条除了查询之外语句。

    • Row: 记录每一行记录修改的形式,也就是记录了哪一行改了,改了啥!就比如你update了100条记录,那它就会记录这100条记录改了啥(5.1.5版本才有)。

    • Mixed:就是statement和row的混合了,由mysql来判断这条语句用哪种形式记录!(5.1.8版本才有)

    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进行主备库同步:

    • 完成2后,还未写binlog,所以该条修改不会写到备库里,redo log为prepare状态,在崩溃恢复时发现该事务并没有完成,会回滚,主库和备库都没有该条修改,保持了一致性。

    • 完成3后,写完binlog,该条修改会同步到备库里,redo log仍为prepare状态,在崩溃恢复时发现redo log有prepare状态并且binlog里也存在该条事务并完整,会提交事务,主库和备库都会有该条修改,保持了一致性。

    注:崩溃恢复的判断规则

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

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

      1. 如果是,则提交事务

      2. 如果否,则回滚事务

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

    • statement格式的最后会有COMMIT

    • raw格式的最后会有xid event

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

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

    • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;

    • 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。

    正常运行的实例,最终落盘的修改数据是从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

    相关文章

      网友评论

        本文标题:Mysql为什么要用两个日志模块?为什么InnoDB采用二段式提

        本文链接:https://www.haomeiwen.com/subject/rnesfltx.html