redo日志

MySql进阶】redo日志详解:redo日志结构、lsn、checkpoint、mysql事务日志、mysql崩溃恢复

数据库 – redo log日志

【mysql】详细分析MySQL事务日志(redo log和undo log的区别)
从MySQL事务和故障恢复角度,清晰弄懂 Undo log、Binlog、Redo log的作用和原理
为什么有binlog还要redo log
在这里插入图片描述

lsn

flush_to_disk_sn和lsn是针对logbuffer;
而checkpointlsn是针对磁盘中的;

因为checkpoint_lsn之前的redo日志都可以被覆盖,这些redo日志对应的脏页都已经被刷新到磁盘中了,所以需要从checkpoint_lsn开始读取redo日志来恢复页面;恢复的时候,读的也是磁盘中的。
redo日志文件组的第一个文件的管理信息中有两个block都存储了checkpoint_lsn的信息,分别是checkpoint1和checkpoint2,选取最近发生的那次checkpoint的信息。利用checkpoint_no衡量发生时间早晚,值越大,说明该block存储的是最近的一次checkpoint信息。

批量从flush链表中刷出脏页
一般情况下都是后台的线程在对LRU链表和flush链表进行刷脏操作,这主要因为刷脏操作比较慢,不想影响用户线程处理请求。但是如果当前系统修改页面的操作十分频繁,这样就导致写日志操作十分频繁,系统lsn值增长过快。如果后台的刷脏操作不能将脏页刷出,那么系统无法及时做checkpoint,可能就需要用户线程同步的从flush链表中把那些最早修改的脏页(oldest_modification最小的脏页)刷新到磁盘,这样这些脏页对应的redo日志就没用了,然后就可以去做checkpoint了。
磁盘上数据页的lsn
每个页上也会记录一个LSN,用于表示已经刷新的数据。即数据页刷到盘上的时候,也会有这个值
在这里插入图片描述
在这里插入图片描述

**

redo log包括两部分:

一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;
二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。

为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MariaDB/MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。

也就是说,从redo log buffer写日志到磁盘的redo log file中;
在这里插入图片描述
另外,之所以要经过一层os buffer,是因为open日志文件的时候,open没有使用O_DIRECT标志位,该标志位意味着绕过操作系统层的os buffer,IO直写到底层存储设备。不使用该标志位意味着将日志进行缓冲(没绕过os buffer),缓冲到了一定容量,或者显式fsync()才会将缓冲中的刷到存储设备。使用该标志位意味着每次都要发起系统调用。比如写abcde,不使用o_direct将只发起一次系统调用,使用o_object将发起5次系统调用(明显经os buffer缓存系统调用的次数少很多)。

redolog的WAL机制

MySQL 每执行一条 SQL 更新语句,不是每次数据更改都立刻写到磁盘,而是先将记录写到 redo log 里面,并更新内存(这时内存与磁盘的数据不一致,将这种有差异的数据称为脏页),一段时间后,再一次性将多个操作记录写到到磁盘上,这样可以减少磁盘 io 成本,提高操作速度。先写日志,再写磁盘,这就是 MySQL 里经常说到的 WAL 技术,即 Write-Ahead Logging,又叫预写日志。MySQL 通过 WAL 技术保证事务的持久性。
问题】:为什么不直接更改磁盘中的数据,而要在内存中更改,然后还需要写日志,最后再落盘这么复杂?、MySQL更改数据的时候,之所以不直接写磁盘文件中的数据,最主要就是性能问题。

因为直接写磁盘文件是随机写,开销大、性能低,没办法满足MySQL的性能要求。所以才会设计成先在内存中对数据进行更改,再异步落盘。

但是内存是不可靠的,万一断电重启,还没来得及落盘的内存数据就会丢失,所以还需要加上写日志这个步骤,万一断电重启,还能通过日志中的记录进行恢复。

日志刷盘的规则

log buffer中未刷到磁盘的日志称为脏日志(dirty log)。

在上面的说过,默认情况下事务每次提交的时候都会刷事务日志到磁盘中,这是因为变量 innodb_flush_log_at_trx_commit 的值为1。但是innodb不仅仅只会在有commit动作后才会刷日志到磁盘,这只是innodb存储引擎刷日志的规则之一。

刷日志到磁盘有以下几种规则:

1.发出commit动作时。已经说明过,commit发出后是否刷日志由变量 innodb_flush_log_at_trx_commit 控制。

2.每秒刷一次。这个刷日志的频率由变量 innodb_flush_log_at_timeout 值决定,默认是1秒。要注意,这个刷日志频率和commit动作无关。

3.当log buffer中已经使用的内存超过一半时。

4.当有checkpoint时,checkpoint在一定程度上代表了刷到磁盘时日志所处的LSN位置。

1.2.1 log buffer中的commit发出后的日志刷入磁盘的策略
MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file(磁盘)中。这种控制通过变量 innodb_flush_log_at_trx_commit 的值来决定。

该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘:

当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。

这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。

当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。

也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。

当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。

在这里插入图片描述
默认值为1,避免数据丢失

3. redo log与binlog的区别

在这里插入图片描述
在这里插入图片描述

redo log
在MySQL InnoDB中,redo log是用来实现事务的持久性,即当事务在提交时,必须先将该事务的所有操作日志写到磁盘上的 redo log file进行持久化,这也就是我们常说的 Write Ahead Log 策略。有了redo log,在数据库发生宕机时,即使内存中的数据还没来得及持久化到磁盘上,我们也可以通过redo log完成数据的恢复,这样就避免了数据的丢失。

binlog:binlog主要用作主从同步和数据库基于时间点的还原
在MySQL中,binlog记录了数据库系统所有的更新操作,主要是用来实现数据恢复和主从复制的。一方面,主从配置的MySQL集群可以利用binlog将主库中的更新操作传递到从库中,以此来实现主从数据的一致性;另一方面,数据库还可以利用binlog来进行数据的恢复。

**区别
第一:redo log是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层产生的,并且二进制日志不仅仅针对INNODB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。

第二:两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。(二进制日志记录操作的方法是逻辑性的语句。即便它是基于行格式的记录方式,其本质也还是逻辑的SQL设置,如该行记录的每列的值是多少。而redo log是在物理格式上的日志,它记录的是数据库中每个页的修改)

第三:二进制日志仅在事务提交时记录,并且对于每一个事务,仅在事务提交时记录,并且对于每一个事务,仅包含对应事务的一个日志。而对于innodb存储引擎的重做日志,由于其记录是物理操作日志,因此每个事务对应多个日志条目,并且事务的重做日志写入是并发的,并非在事务提交时写入,其在文件中记录的顺序并非是事务开始的顺序。

第四:binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redo log是循环使用。

第五:binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用

innodb的恢复行为

在这里插入图片描述

redo和binlog恢复的速度:
在启动innodb的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。

因为redo log记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如二进制日志)要快很多。而且,innodb自身也做了一定程度的优化,让恢复速度变得更快。

另外,事务日志具有幂等性,所以多次操作得到同一结果的行为在日志中只记录一次。而二进制日志不具有幂等性,多次操作会全部记录下来,在恢复的时候会多次执行二进制日志中的记录,速度就慢得多。例如,某记录中id初始值为2,通过update将值设置为了3,后来又设置成了2,在事务日志中记录的将是无变化的页,根本无需恢复;而二进制会记录下两次update操作,恢复时也将执行这两次update操作,速度比事务日志恢复更慢。
redolog如何崩溃恢复的数据

  1. 恢复的位置点:重启innodb时,checkpoint表示已经完整刷到磁盘上data page上的LSN,因此恢复时仅需要恢复从checkpoint开始的日志部分。例如,当数据库在上一次checkpoint的LSN为10000时宕机,且事务是已经提交过的状态。启动数据库时会检查磁盘中数据页的LSN,如果数据页的LSN小于日志中的LSN,则会从检查点开始恢复。
  2. 对于需要恢复的部分:通过redo日志将所有已经在存储引擎内部提交commit的事务应用redo log恢复;所有已经prepare但是没有commit的transactions将会,以binlog中的xid和redo log中的xid进行比较,xid在binlog里存在则提交,不存在则应用undo log做rollback。然后客户端连接时就能看到已经提交的数据存在数据库内,未提交被回滚地数据需要重新执行。
  3. 还有一种情况,在宕机前正处于checkpoint的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度。这时候一宕机,数据页中记录的LSN就会大于日志页中的LSN,在重启的恢复过程中会检查到这一情况,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。

LSN
LSN 是 日志序列号, 为 log sequence number 的缩写,主要用于发生 crash 时对数据进行 recovery。LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。
LSN 不仅只存在于重做日志中,在每个数据页头部也会有对应的 LSN 号,该 LSN 记录当前页最后一次修改的 LSN 号,用于在 recovery 时对比重做日志 LSN 号决定是否对该页进行恢复数据。
前面说的check point也是由 LSN 号记录的,LSN 号串联起一个事务开始到恢复的过程。

DB宕机后重启,InnoDB 会首先去查看数据页中的LSN的数值。这个值代表数据页被刷新回磁盘的 LSN 的大小。然后再去查看 redo log 的 LSN 的大小。
如果数据页中的 LSN 值大说明数据页领先于 redo log 刷新回磁盘,不需要进行恢复。反之需要从redo log中恢复数据。

例子:
在不同的阶段时刻,看看MySQL突然奔溃后,按照上述流程是如何恢复数据的。

  1. 时刻A(刚在内存中更改完数据页,还没有开始写redo log的时候奔溃): 因为内存中的脏页还没刷盘,也没有写redo log和binlog,即这个事务还没有开始提交,所以奔溃恢复跟该事务没有关系;
  2. 时刻B(正在写redo log或者已经写完redo log并且落盘后,处于prepare状态,还没有开始写binlog的时候奔溃): 恢复后会判断redo log的事务是不是完整的,如果不是则根据undo log回滚;如果是完整的并且是prepare状态,则进一步判断对应的事务binlog是不是完整的,如果不完整则一样根据undo log进行回滚;
  3. 时刻C(正在写binlog或者已经写完binlog并且落盘了,还没有开始commit redo log的时候奔溃): 恢复后会跟时刻B一样,先检查redo log中是完整并且处于prepare状态的事务,然后判断对应的事务binlog是不是完整的,如果不完整则一样根据undo log回滚,完整则重新commit redo log;
  4. 时刻D(正在commit redo log或者事务已经提交完的时候,还没有反馈成功给客户端的时候奔溃): 恢复后跟时刻C基本一样,都会对照redo log和binlog的事务完整性,来确认是回滚还是重新提交。
    ————————————————

为什么binlog不能用来崩溃恢复:

由 binlog 和 redo log 的区别可知:binlog 日志只用于归档,只依靠 binlog 是没有 crash-safe 能力的。为什么 binlog 没有 crash-safe 能力,当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志,但没有一个标志让 innoDB 判断哪些数据已经刷盘,哪些数据还没有。但 redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后,直接把 redo log 中的数据都恢复至内存就可以了(为什么要恢复到内存,因为最终是通过内存buffer pool的刷磁盘最终将脏数据落盘到磁盘中,这里可以看下一章)。这就是为什么 redo log 具有 crash-safe 的能力,而 binlog 不具备。但只有 redo log 也不行,因为 redo log 是InnoDB 特有的,换了一个储存引擎就没有redo log了,且日志上的记录落盘后会被覆盖掉。因此需要 binlog 和 redo log 二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。

binlog可以去掉吗?这里需要分场景来看:

  1. 如果是主从模式下,binlog是必须的,因为从库的数据同步依赖的就是binlog;
  2. 如果是单机模式,并且不考虑数据库基于时间点的还原,binlog就不是必须,因为有redo log就可以保证crash-safe能力了;但如果万一需要回滚到某个时间点的状态,这时候就无能为力,所以建议binlog还是一直开启;

redolog和binlog的二阶段提交

在这里插入图片描述

先写binlog还是先写redo log的呢?
针对这个疑问,我们可以做出两个假设。

假设一:先写redo log再写binlog
想象一下,如果数据库系统在写完一个事务的redo log时发生crash,而此时这个事务的binlog还没有持久化。在数据库恢复后,主库会根据redo log中去完成此事务的重做,主库中就有这个事务的数据。但是,由于此事务并没有产生binlog,即使主库恢复后,关于此事务的数据修改也不会同步到从库上,这样就产生了主从不一致的错误。

假设二:先写binlog再写redo log
想象一下,如果数据库系统在写完一个事务的binlog时发生crash,而此时这个事务的redo log还没有持久化,或者说此事务的redo log还没记录完(至少没有记录commit log)。在数据库恢复后,从库会根据主库中记录的binlog去回放此事务的数据修改。但是,由于此事务并没有产生完整提交的redo log,主库在恢复后会回滚该事务,这样也会产生主从不一致的错误。

通过上面的假设和分析,我们可以看出,不管是先写redo log还是先写binlog,都有可能会产生主从不一致的错误,那么MySQL又是怎么做到binlog和redo log的一致性的呢?

在MySQL内部,在事务提交时利用两阶段提交(内部XA的两阶段提交)很好地解决了上面提到的binlog和redo log的一致性问题:

第一阶段: InnoDB Prepare阶段。此时SQL已经成功执行,并生成事务ID(xid)信息及redo和undo的内存日志。此阶段InnoDB会写事务的redo log,但要注意的是,此时redo log只是记录了事务的所有操作日志,并没有记录提交(commit)日志,因此事务此时的状态为Prepare。此阶段对binlog不会有任何操作。
第二阶段:commit 阶段,这个阶段又分成两个步骤。第一步写binlog(先调用write()将binlog内存日志数据写入文件系统缓存,再调用fsync()将binlog文件系统缓存日志数据永久写入磁盘);第二步完成事务的提交(commit),此时在redo log中记录此事务的提交日志(增加commit 标签)。 还要注意的是,在这个过程中是以第二阶段中binlog的写入与否作为事务是否成功提交的标志。第一步写binlog完成 ,第二步redo log的commit未提交未完成,也算完成。
可以看出,此过程中是先写redo log再写binlog的,binglog刷盘后,此时通知relog更改状态,称之为二次提交,这样保证了binlog和redolog的一致性。但需要注意的是,在第一阶段并没有记录完整的redo log(不包含事务的commit标签),而是在第二阶段记录完binlog后再写入redo log的commit 标签。

此时的崩溃恢复过程如下:

如果数据库在记录此事务的binlog之前和过程中发生crash。数据库在恢复后认为此事务并没有成功提交,则会回滚此事务的操作。与此同时,因为在binlog中也没有此事务的记录,所以从库也不会有此事务的数据修改。

如果数据库在记录此事务的binlog之后发生crash。此时,即使是redo log中还没有记录此事务的commit 标签,数据库在恢复后也会认为此事务提交成功(因为在上述两阶段过程中,binlog写入成功就认为事务成功提交了)。它会扫描最后一个binlog文件,并提取其中的事务ID(xid),InnoDB会将那些状态为Prepare的事务(redo log没有记录commit 标签)的xid和Binlog中提取的xid做比较,如果在Binlog中存在,则提交该事务,否则回滚该事务。这也就是说,binlog中记录的事务,在恢复时都会被认为是已提交事务,会在redo log中重新写入commit标志,并完成此事务的重做(主库中有此事务的数据修改)。与此同时,因为在binlog中已经有了此事务的记录,所有从库也会有此事务的数据修改。

简单来说,即使是redo log中还没有记录此事务的commit 标签,只要记录了binlog,系统重启后,就会去扫描binlog,重新补上redo log的 commit

知识点总结:

自动为每个事务分配一个唯一的ID(XID)。这个id很重要,是两阶段提交的核心

COMMIT会被自动的分成Prepare和Commit两个阶段。

Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。
MySQL 中 Redo 日志与 Binlog 日志顺序一致性问题
InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值