了解mysql三大日志:binlog、redolog、undolog

mysql日志主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。作为开发,我们重点需要关注的是二进制日志( binlog )和事务日志(包括redo log 和 undo log )。

binlog

二进制日志(BinLog)是记录MySQL实例数据变更的一个组件,日志中包含了一系列变更数据的操作,例如变更表结构、删除数据、更改/添加数据等DML、DDL。这些操作在binlog中统一称为事件(Events)。Binlog也会记录一些可能会发生数据变更的事件,例如没有找到对应行的Delete操作。但是,BinLog不会记录纯查询语句,例如Select和Show。如果需要排查这些语句的记录,需要查找通用语句日志。

场景

Binlog主要用于两个场景

主从同步:在主从同步的过程中,Binlog用于记录主库的数据变更,然后这些记录被主库内的线程发送至从库。从库的工作线程再把接收到的变更事件放到从库上执行,完成数据同步。主从同步通常被视为提升数据库吞吐能力的一种方法,因此Binlog是必不可少的环节。

数据恢复: 在生产环境中,总是会有意外导致数据丢失。在一些数据恢复的场景中,Binlog是必不可少的。当数据库从备份中恢复的时候,binlog中所记录的信息会在恢复后的数据上执行,补齐备份数据中未备份的记录。

组成

一个完整的binlog由两种文件组成
索引文件(Index File):
索引文件用于跟踪多个binlog文件,便于主库创建新的binlog文件。索引文件中的每一行记录着所有关联和它关联的binlog文件名。
日志文件(Binlog file):
日志文件是binlog的主体,如上图所示,它是由一系列事件(Binary Log Events)组成。

日志文件的开头记录的是的是 Format_description 事件,这个事件记录主库的信息和日志文件的状态。如果主库突然宕机或者重启,主库会重新创建一个日志文件然后在开头写入Format_description。
当主库记录完成变更事件后,主库会写入Rotate事件。Rotate事件会指定下一个日志文件的文件名和读取事件的起始点。

除开上述的Format_description事件和Rotate事件,日志文件都会把其他的变更事件进行分组(Group)。在MySQL中,每一个事务会被分成一组,组中包含了这个事务下执行的所有语句。一些非事务性语句会被单独分成一组,如Create和Alter语句等。

日志格式

自从MySQL5.1起,日志的格式增加到了三种

Statement-based: 这是MySQL默认的日志格式。在这个格式下,binlog日志中记录的是变更数据库的执行语句和事务。

Row-based: 在这个格式下,binlog日志中记录的是发生变更的数据行。

Mixed-logging: 这个格式是前两种格式的混合版,主库会根据执行的语句来决定binlog日志中记录的内容,可以是具体的行,也可以是执行的语句。

使用过程

1.主从同步(详细参考之前
不同的binlog日志格式会影响从库的同步方式。在statement-based格式下,从库是直接执行binlog日志中读取到的语句或者事务。在row-based格式下,从库是根据日志内容直接更新对应的数据。

MySQL默认的日志格式是statement-based。在大多数主从同步的场景下,日志格式采用row-based,最主要的原因是row-based格式下,日志内容都是真实发生变更的数据,从库的数据准确性有很高的保证。

2.数据恢复
我们知道mysql会对数据库进行持久化备份,当数据意外丢失后,备份数据可以拿过来恢复。然而,即使数据做了备份,也不能完全恢复到丢失那一刻的数据。
例如每半个小时备份一次,在备份的前一刻crash了,我们可能会丢失最后半个小时的数据。

这里就用到了MySQL的Point-In-time recovery(简称PITR)功能,PITR是用于恢复某个时间点的失效数据,用于弥补备份时间点到失效时间点这段数据“真空期”,实现这个功能的核心是binlog日志文件。

binlog日志中存放着数据库所有的变更。PITR的原理很简单,就是将binlog内存储的变更数据重新执行一遍。需要恢复数据时,用mysqlbinlog命令执行binlog日志的内容。
(刷新到磁盘的行为由参数sync_binlog决定)

redolog

redo log是什么

我们都知道,事务的四大特性里面有一个是 持久性 ,具体来说就是
只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态 。那么 mysql是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:

因为 Innodb 是以 页 为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!
因此 mysql 设计了 redo log , 具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)。

组成

redo 日志记录的是每个页面(page)更改物理情况,所以 redo 日志整体来说是比较小的,存储的信息不多,简单的介绍一下存储字段的意思:

  • type:该条redo日志的类型。
  • space ID:表空间ID。
  • page number:页号。
  • data:该条redo日志的具体内容。

redo 日志并非这么简单,它非常的复杂,但是我们不需要对它庖丁解牛,因为它确实对我们来说没啥用,我们只要记住 redo 日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。

WAL

WAL(Write-ahead logging,预写式日志)是数据库系统提供原子性和持久化的一系列技术。

在使用WAL的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中。
在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的 redo 日志刷新到磁盘的好处如下:

redo日志占用的空间非常小:存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的
redo日志是顺序写入磁盘的:在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO。

redolog和binlog的数据恢复场景

redo log和binlog的区别

  • redo log和bin log的产生方式不同。redo log是在物理存储引擎层产生,而bin log是在MySQL数据库的Server层产生的,并且bin log不仅针对InnoDB存储引擎,MySQL数据库中的任何存储引擎对数据库的更改都会产生bin log。
  • redo log和binlog的记录形式不同。MySQL Server层产生的bin log记录的是一种逻辑日志,即通过SQL语句的方式来记录数据库的修改;而InnoDB层产生的redo log是一种物理格式日志,其记录的是对于磁盘中每一个数据页的修改。
  • redo log和bin log记录的时间点不同。bin log只是在事务提交完成后进行一次写入,而redo log则是在事务进行中不断地被写入,redo log并不是随着事务提交的顺序进行写入的,这也就是说在redo log 中针对一个事务会有多个不连续的记录日志。
  • redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
  • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用

binlog和redolog都有恢复能力,能否去掉一个

首先是MySQL体系结构的原因,MySQL是多存储引擎的,不管使用那种存储引擎,都会有binlog,而不一定有redo log,简单的说,binlog是MySQL Server层的,redo log是InnoDB层的。

只使用binlog行不行?
binlog 日志只用于归档,只依靠 binlog 是没有 crash-safe 能力的。因此innodb引入了redolog

只使用redolog行不行?
redo log是恢复在内存更新后,还没来得及刷到磁盘的数据
binlog是存储所有数据变更的情况,理论上只要记录在binlog上的数据,都可以恢复。

如不小心整个数据库的数据被删除了,能使用redo log文件恢复数据吗?
不可以使用redo log文件恢复,只能使用binlog文件恢复。因为redo log文件不会存储历史所有的数据的变更,当内存数据刷新到磁盘中,redo log的数据就失效了,也就是redo log文件内容是会被覆盖的。

为什么说redolog 具有crash-safe能力,而 binlog 没有?

redo log 是什么?
一个固定大小,“循环写”的日志文件,记录的是物理日志——“在某个数据页上做了某个修改”。

binlog 是什么?
一个无限大小,“追加写”的日志文件,记录的是逻辑日志——“给 ID=2 这一行的 c 字段加1”。

redo log 和 binlog 有一个很大的区别就是,一个是循环写,一个是追加写。也就是说 redo log 只会记录未刷盘的日志,已经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是追加日志,保存的是全量的日志。

当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志,但没有一个标志让 innoDB 判断哪些数据已经刷盘,哪些数据还没有。

举个栗子,binlog 记录了两条日志:

给 ID=2 这一行的 c 字段加1
给 ID=2 这一行的 c 字段加1
在记录1刷盘后,记录2未刷盘时,数据库 crash。重启后,只通过 binlog 数据库无法判断这两条记录哪条已经写入磁盘,哪条没有写入磁盘,不管是两条都恢复至内存,还是都不恢复,对 ID=2 这行数据来说,都不对。

但 redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后,直接把 redo log 中的数据都恢复至内存就可以了。这就是为什么 redo log 具有 crash-safe 的能力,而 binlog 不具备。

undolog

数据库事务四大特性中有一个是 原子性 ,具体来说就是 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。
实际上, 原子性 底层就是通过 undo log 实现的。 undo log 主要记录了数据的逻辑变化,比如一条 INSERT语句,对应一条 DELETE 的 undo log ,对于每个 UPDATE 语句,对应一条相反的 UPDATE 的undo log ,这样在发生错误时,就能回滚到事务之前的数据状态。同时, undo log 也是 MVCC (多版本并发控制)实现的关键。

log写入时机

binlog 写入

binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。

那么 biglog是什么时候刷到磁盘中的呢? mysql 通过 sync_binlog 参数控制 biglog 的刷盘时机,取值范围是 0-N

sync_binlog含义
0不去强制要求,由系统自行判断何时写入磁盘;
1每次 commit 的时候都要将 binlog 写入磁盘;
N每N个事务,才会将 binlog 写入磁盘。

从上面可以看出, sync_binlog 最安全的是设置是 1 ,这也是 MySQL 5.7.7之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能。

redolog 写入

redo 日志同样也要先写入到缓存区,我们把这个缓冲区叫做 redo日志缓冲区。在服务器启动时就向操作系统申请了一大片称之为 redo log buffer 的连续内存空间。
如果我们想要提交一个事务了,此时就会根据一定的策略把 redo 日志从 redo log buffer 里刷入到磁盘文件里去。此时这个策略是通过 innodb_flush_log_at_trx_commit 来配置的,他有几个选项。

innodb_flush_log_at_trx_commit含义
0提交事务的时候,不立即把 redo log buffer 里的数据刷入磁盘文件的,而是依靠 InnoDB 的主线程每秒执行一次刷新到磁盘。此时可能你提交事务了,结果 mysql 宕机了,然后此时内存里的数据全部丢失。
1提交事务的时候,就必须把 redo log 从内存刷入到磁盘文件里去,只要事务提交成功,那么 redo log 就必然在磁盘里了。注意,因为操作系统的“延迟写”特性,此时的刷入只是写到了操作系统的缓冲区中,因此执行同步操作才能保证一定持久化到了硬盘中。
2提交事务的时候,把 redo 日志写入磁盘文件对应的 os cache 缓存里去,而不是直接进入磁盘文件,可能 1 秒后才会把 os cache 里的数据写入到磁盘文件里去。

可以看到,只有1才能真正地保证事务的持久性,但是由于刷新操作 fsync() 是阻塞的,直到完成后才返回,我们知道写磁盘的速度是很慢的,因此 MySQL 的性能会明显地下降。如果不在乎事务丢失,0和2能获得更高的性能。

Undolog写入时机

其实undolog和redolog的写入时机是十分相似的,但是我们可以理解为undolog每次都会落盘,redolog会先写入redolog_buffer,在事务提交后落盘

其实undolog也会生成一个redolog,用于保证undolog的持久性,这里就不谈了

redolog和binlog一致性问题

一个事物完成后,redolog和binlog都需要落盘,那么谁先谁后有影响么?
MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性。

如果在crash时redolog和binlog有一个落盘而另一个没有落盘,就会产生主从不一致的问题:

1.先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。

2.先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

如果想保证主从的一致性,必须保证这两个日志,要么都落盘,要么都失败

所以在开启Binlog后,如何保证binlog和InnoDB redo日志的一致性呢?为此,MySQL引入二阶段提交(two phase commit or 2pc),MySQL内部会自动将普通事务当做一个XA事务(内部分布式事务)来处理。
在最后提交事务的时候,需要有3个步骤:
1.redo log落盘,处于prepare状态
2.binlog落盘
3.修改redo log状态为commit
ps: redo log的提交分为prepare和commit两个阶段,所以称之为两阶段提交

当数据恢复时,如果一个事务redolog处于prepare阶段,会先检查它有没有完整的binlog,如果有我们会认为该事务执行成功,没有,我们认为该事务没有执行成功

我们可以模拟一下crash时机,当步骤1之前发生crash,redolog和binlog都没有,此时需要undolog进行回滚,当步骤1和步骤3之间发生crash,那就是那就是上述红字的情况,此时有redolog,但出于prepare状态,按照binlog的存在与否决定使不使用该redolog

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值