前言:

MySql 中有三种 log 是十分中要的,因为MySql之所以能支持 事务(实现持久化、回滚等)、数据库崩溃恢复、主从复制等,都是基于这三种日志的。

至关重要的三种log:

  • binlog 二进制日志
  • redo log 重做日志
  • undo log 回滚日志

本文主线:

  • 简要介绍三种日志
  • MySql事务处理中三种日志承担的角色

简要介绍三种日志:

1、binlog 二进制日志:

binlog 二进制日志(归档日志),这个日志是由MySql的 server层 进行维护的;不管当前MySql使用的是什么存储引擎,binlog归档日志都是支持的;

对MySql的server层不清楚的大大们可以参考此文章:查询SQL的执行流程

作用:

  • 用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
  • 数据恢复,用于数据库的基于时间点的还原。

内容:

逻辑格式的日志,binlog是用于记录所有数据库表结构和表数据变更的二进制日志,比如insert、update、delete、create、truncate等等操作,不会记录select、show操作,因为它们没有对数据本身发生改变。

常见格式:

MySql的binlog的默认格式是使用 STATEMENT;需要记住的是使用此格式时,如果将事务隔离级别设置为 RC读已提交 时,在进行主从复制的时候会存在bug,导致复制后主从数据不一致;

具体存在什么样的bug可以参考此文章:互联网项目中mysql应该选什么事务隔离级别

注意:

binlog二进制日志文件在默认情况下并没有启动,需要手动进行开启的;有人可能会质疑,开启此日志文件的话,对数据库的性能会产生什么样的影响?

质疑是对的,确实开启此日志文件会影响到数据库的性能,但是这个影响是十分有限的,根据MySql的官网手册了解到,开启此日志会使性能下降1%左右,这个损失大体上是可以接受的。

2、redo log 重做日志:

redo log重做日志,这个日志是由MySql的 innodb存储引擎 提供维护的,此日志文件只存在于innodb存储引擎下;

作用:

确保事务的 持久性 。redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

各位大大,如果上面这段话如果看的不是很明白,可以继续往下看看呀,通过下文中事务的例子可以很好理解;

内容:

物理格式的日志,记录的是物理数据页面的修改的信息,简单说就是记录着xxx页做了xxx修改;

innodb 存储引擎提供了重做日志文件组(group),每个重做日志文件组包含着重做日志文件;默认是提供了一个重做日志文件组,文件组下包含两个大小相同的重做日志文件;

innodb存储引擎的重做日志文件写入流程:先写重做日志文件1,当文件1被顺序写满时,会切换到重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中,依次循环; 所以说重做日志文件是 循环覆盖写入的

因为重做日志是循环覆盖写入的,所以不能使用其进行整个数据库的数据恢复,它只能保证数据库宕机时的事务的完整性数据;如果想要恢复全部数据的话,只能使用 binlog 二进制日志(归档日志)进行恢复。

注意:大家可以手动修改重做日志文件组下的文件数量,并可以指定每个重做日志文件的大小,通过下面的参数:

  • innodb_log_file_size 指定重做日志文件的大小
  • innodb_log_files_in_group 重做日志文件组下的文件数量

扩展:

注意:重做日志文件的大小设置对于innodb存储引擎的性能有很大的影响。

重做日志文件不能设置的太大,也不能设置的太小;如果设置的太大,在数据库意外宕机后进行恢复时会需要很长时间;

但是也不能设置的太小,因为设置的太小会导致一个事务的日志需要多次切换重做日志文件进行写入,那么在覆盖掉之前的重做日志时,需要将要被覆盖的重做日志对应在内存中的脏页进行写盘(刷盘);

因为不写盘的话,如果重做日志被覆盖掉了,然后数据库意外宕机了,那么之前没有写盘的数据将没法在数据库重启时进行恢复了,并且如果频繁的进行重做日志的覆盖的话,那么就会频繁的进行脏页刷盘,进而导致数据库性能的抖动。

那重做日志应该设置多大呢? 一般来说,redo log日志文件的全部大小,应该足够容纳服务器一个小时的活动内容。

如果统计出一小时的重做日志写入量为 500M 的话,由于 redo log 日志文件默认有 2 个,所以需要设置 innodb_log_file_size=250M ;

注意:如果各位大大们对 脏页等名词 不太清楚的话,可以通过《丁奇大佬 - MySQL实战45讲》、《mysql技术内幕 innodb存储引擎》进行学习了解呀。

3、undo log 回滚日志:

注意 undo log 回滚日志的写入是在事务开始执行前就已经开始了;

作用:

保证数据的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读;

内容:

逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。

比如说我们要insert一条数据,那么undo log就会生成一条对应的delete日志,并且与当前事务的txid(事务唯一标识)进行了关联,用于支持回滚和MVCC;

像如果在一个事务中执行了多个包含update、insert、delete的语句时,由于事务具备原子性,如果其中一个语句执行失败,那么之前执行成功的语句要就要被回滚撤销掉,此时就会根据当前事务txid在undo log中查找到保存的日志进行执行回滚;

注意:写入undo log 的这个操作也是需要记录到redo log重做日志中的;

为什么也要记录到redo log中呢? 举个场景,当事务在没执行完时mysql意外宕机了,那么在数据库重启后需要恢复事务,如果事务需要回滚,但是你在内存中保存的undo log还没有写入磁盘,在宕机时就丢失了,那么就会导致无法回滚,就会出现数据不一致的问题;而如果记录到了redo log中,则可以根据redo log进行重做,然后根据重做的undo log进行回滚。

扩展:

注意:为了提升数据库性能,mysql都是在内存的缓冲区中记录日志,然后根据策略再进行刷盘的(将缓冲区记录的日志写入到磁盘中进行保存);

通过innodb存储引擎的架构图可以进行深入了解:

如果想深入学习,请参考此文章 你居然还不知道Mysql存储引擎InnoDB分为内存架构、磁盘架构?

MySql事务处理中三种日志承担的角色:

首先咱通过一个update语句的执行流程图展示出三种日志文件在事务处理中承担的角色! SQL语句如下:

update T set c=c+1 where ID=2;

为什么流程图中没有undo log:

在流程图中可以看到redo log、binglog,那怎么没有undo log 呀,因为 undo log在 事务开始执行前 就已经开始写入了 。

为什么上图事务中必须存在redo log 和 binlog呢:

首先,上面介绍这两种日志时,已经说道了binlog 是mysql的server层维护的日志,主要用来做主从复制和数据备份恢复使用;而redo log是InnoDB存储引擎独有的日志,是用来实现crash-safe能力;

crash-safe 是什么?

即在 InnoDB 存储引擎中,事务提交过程中任何阶段,MySQL突然奔溃,重启后都能保证事务的完整性,已提交的数据不会丢失,未提交完整的数据会自动进行回滚。

为了实现crash-safe除了需要redo log外,还需要 undo log对未完整的事务进行回滚的;

想要对crash-safe进行详细了解的大大们可以参考此文章:MySQL 的 crash-safe 原理解析

上图中处于prepare阶段、处于commit阶段是什么:

其实这是mysql的内部XA事务!俗称日志的两阶段提交协议!看到这大家是不是有点模糊了,听说过分布式XA事务,怎么还有内部XA事务呀?

其实大家知道的分布式XA事务指的是mysql的外部XA事务;内部xa事务主要是mysql内部为了保证binlog与redo log之间数据的一致性而存在的,这也是由其架构决定的(binlog由mysql的server层提供支持,而redo log 则是由 innodb存储引擎层提供支持);

对mysql的架构不太熟悉的大大可以参考此文章:查询SQL的执行流程

binlog与redo log之间数据的一致性问题是什么:

因为redo log 和 binlog 都是用来"恢复" 数据的,redo log是用来恢复事务,保证事务的完整性的,而 binlog 是可以用来进行整体数据恢复,或基于某个时间点进行恢复的,并且用于主从复制;

如果在进行一个事务执行时,已经完成了 redo log重做日志的写入,但是还没有写入binlog,此时数据库意外宕机了,如果没有保障两个日志 逻辑上一致 的话;

那么在数据库重启时会根据redo log进行事务恢复,并将在事务中已经处理好但未写盘的数据进行恢复;但是由于binlog没有写入,所以会导致在使用binlog进行数据恢复时,或者是主从复制时,出现数据不一致的情况;

如果保证了两个日志的逻辑一致的话,那么在使用redo log恢复事务时,会判断binlog是否也写入成功了,如果binlog也写入成功,那么才会进行事务恢复,否则将不进行事务恢复,会使用undo log 进行回滚的;

MySql内部XA事务是什么呢:

内部XA事务就是将事务提交分为了两个阶段,prepare阶段和commit阶段!

prepare:写入redo log,并将回滚段置为prepared状态,此时binlog不做操作。

commit:innodb释放锁,释放回滚段,设置提交状态,写入binlog,然后存储引擎层提交。

扩展:

各位大大们,上图update的语句执行流程中,写入redo log重做日志,写入binglog二进制日志,其实都只是 写入到日志缓冲区中,后面还会涉及到日志缓冲区中数据写入到具体磁盘中的日志文件中;

那何时才会将缓冲区的日志数据写入到磁盘中?这是由mysql中的几个配置参数控制的;

缓冲区的日志写入到磁盘中的流程:

在介绍配置参数前,咱们先了解下缓冲区的日志写入到磁盘中的流程是什么样:

  • 用户空间:是指用户程序运行的空间,例如mysql就运行在此空间内;

  • 内核空间: 内核空间是指操作系统内核运行的空间,是为了保证操作系统内核的能够安全稳定地运行而为内核专门开辟的空间;

为什么需要内核空间呢?

主要是为了安全起见,不能让用户空间的程序直接去磁盘空间中读取数据,必须由经由内核空间通过DMA来获取;并且用户空间和内核空间两者之间是互相隔离的 。

redo log刷盘配置参数:

innodb_flush_log_at_trx_commit 配置参数用来控制重做日志刷新到磁盘的策略。

该参数的值存在三种:

  • 0:表示事务提交时不进行写入重做日志擦操作,MySql会使用其后台线程每一秒将日志缓冲区中的重做日志写入到 OS cache(磁盘缓存),同时立即调用 fsync 操作将 OS cache 中的重做日志写入到磁盘文件中(刷盘);

  • 1:表示在事务提交时就会进行重做日志的写入操作,实时的将日志缓冲区中的重做日志写入到 OS cache(磁盘缓存),同时立即调用 fsync 操作将 OS cache 中的重做日志写入到磁盘文件中(刷盘); 此值为默认值 ,因为当设置值为1时可以保证事务的ACID中的持久性;

  • 2:表示在事务提交时就会进行重做日志的写入操作,但是只是将日志缓冲区中的重做日志写入到 OS cache(磁盘缓存),不会立即调用 fsync 操作进行刷盘,MySQL后面会主动将OS cache中的重做日志数据每秒批量进行一次刷盘; 选择此值时,mysql的并发性最好,但是存在风险,当操作系统一旦宕机,会丢数据,但是如果MySql数据库宕机的话,则不会丢失数据,因为数据保存在了OS cache中;

binlog 刷盘配置参数:

sync_binlog 配置参数用来控制二进制日志刷新到磁盘的策略。

该参数常用的三种值:

  • 0:表示在事务提交后,mysql会将日志缓冲区中的binlog数据写入到 OS cache(磁盘缓存),但并不会调用 fsync 操作进行刷盘,而是由操作系统自己控制它的缓存的刷盘; 此值为默认值

  • 1:表示在事务提交时就会进行binlog的写入操作,实时的将日志缓冲区中的重做日志写入到 OS cache(磁盘缓存),同时立即调用 fsync 操作将 OS cache 中的重做日志写入到磁盘文件中(刷盘);

  • N:表示每次进行提交后都只是将日志缓冲区中的binlog数据写入到 OS cache(磁盘缓存),在进行N次事务提交以后,Mysql将执行一次fsync操作指令将OS cache中的binlog数据批量刷新到磁盘中; 注意 N 代表的是数值;

一道经典的面试题:

面试官:你有遇到过数据库宕机重启,事务丢失的情况么?

我们:其实这道题就是redo log、binlog的刷盘时机,上面已经聊了这两种日志的全部刷盘时机了,大家可以两两结合进行回答就好了;

也可以直接参考此文中提供的答案进行回答:面试官:谈谈你对mysql事务的认识?

各位大大们,MySql中至关重要的三种日志就介绍到此啦;由于本人水品有限,如有问题请留言讨论呀!

点赞 + 评论 + 转发 哟

如果本文对您有帮助的话,请挥动下您爱发财的小手点下赞呀,您的支持就是我不断创作的动力,谢谢啦!

您可以微信搜索 【木子雷】 公众号,大量Java学习干货文章,您可以来瞧一瞧哟!