redo log 漫游

redo log漫游

redo log -> 物理日志

  1. redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
  2. redo log是InnoDB引擎独有的
  3. redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。redo log在数据准备修改前写入缓存中的redo log中,然后才对缓存中的数据执行修改操作;而且保证在发出事务提交指令时,先将缓存中的redo log写入日志,写入完成后才执行提交动作。
  4. redo log的写入是使用了WAL(Write-Ahead Logging)技术。
  5. redo log 记录的是物理页的情况,具有幂等性,因此记录的日志极其简练。

WAL:用户如果对数据库中的数据进行了修改,必须保证日志先于数据落盘。当日志落盘后,就可以给用户返回操作成功,并不需要保证当时对数据的修改也落盘。如果数据库在日志落盘前crash,那么相应的数据修改会回滚。在日志落盘后crash,会保证相应的修改不丢失。

日志刷入磁盘

为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件中的过程中,都会调用一次操作系统的fsync操作。Mysql是工作在用户空间的,所以log buffer处于用户空间的内存中。要写入磁盘中的log file中,中间需要经过操作系统的内核空间os buffer,调用fsync将OS buffer中的日志刷到磁盘中的log file中。

innodb engine.png

用户可以自定义在commit的时候如何将log buffer中的日志刷入log file中。这种控制通过变量innodb_flush_log_at_trx_commit的值来决定。该变量有3种植:0,1,2,默认为1.但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。

  • 当设置为0的时候,事务提交时不会将log buffer中的日志写入到OS buffer,而是每秒写入OS buffer,并调用fsync()写入到log file中。也就是说设置为0时,系统崩溃,会丢失1秒的数据。
  • 当设置为1的时候,事务每次提交都会将log buffer中的日志写入OS buffer并调用fsync()刷到log file中。这种方式即使系统崩溃都不会丢失数据,但是每次提交都写入磁盘,IO的性能较差。
  • 当设置为2的时候,每次提交都仅写入到OS buffer,然后每秒调用fsync()将OS buffer中的日志写入log file on disk。

log 内存.png

数据页刷盘的规则及checkpoint

内存中(buffer pool)未刷到磁盘的数据称为脏数据(dirty data)。由于数据和日志都以页的形式存在,所以脏页表示脏数据和脏日志。

如果 redo log 可以无限地增大,同时缓冲池也足够大,意味着可以不将缓冲池中的脏页刷新回磁盘上。宕机时,完全可以通过 redo log 来恢复整个数据库系统中的数据。

显然,上述的前提条件是不满足的,这也就引入了 checkpoint 技术。

Checkpoint (检查点) 的目的是为了解决以下几个问题:1、缩短数据库的恢复时间;2、缓冲池不够用时,将脏页刷新到磁盘;3、redo log不可用时,刷新脏页。

  • 数据库宕机时,不需要重做所有的日志,因为checkpoint之前的脏页都已经刷新到磁盘了,只需要对CheckPoint后的redo log 进行恢复即可,这样就打打缩短了恢复的时间。
  • 当缓冲池不够用时,会根据LRU算法淘汰最近最少使用的页,若此页为脏页,那么需要强制执行 Checkpoint,将脏页刷回磁盘。
  • 当前数据库对redo log的设计都是循环使用的,为了防止被覆盖,必须强制Checkpoint,将缓冲池中的页刷新到当前redo log的位置。
    UTOOLS1569224209386.png

    Checkpoint的分类
  1. sharp checkpoint:在重用redo log文件(例如切换日志文件,正常关闭数据库)的时候,将所有已记录到redo log中对应的脏数据刷到磁盘。
  2. fuzzy checkpoint:一次只刷一小部分的日志到磁盘,而非将所有脏日志刷盘。有以下几种情况会触发该检查点:
    1. Master Thread Checkpoint:InnoDB 的主线程以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘,这个过程是异步的,此时 InnoDB 可以进行其他的操作,用户查询线程不会阻塞。
    2. flush_lru_list checkpoint:从MySQL5.6开始可通过 innodb_page_cleaners 变量指定专门负责脏页刷盘的page cleaner线程的个数,该线程的目的是为了保证lru列表有可用的空闲页。空闲页为变量 innodb_lru_scan_depth 控制
    3. async/sync flush checkpoint:在redo log日志文件不可用时,强制将脏页列表中的一些页刷新回磁盘,而此时脏页是从脏页列表中选取的,保证redo log文件可循环使用。
    4. dirty page too much checkpoint:脏页太多时强制触发检查点,目的是为了保证缓存有足够的空闲空间。too much的比例由变量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默认的值为75,即当脏页占缓冲池的百分之75后,就强制刷一部分脏页到磁盘。

由于刷脏页需要一定的时间来完成,所以记录检查点的位置是在每次刷盘结束之后才在redo log中标记的。

sharp checkpoint发生时记录LSN(log sequence number )到最后一个提交的事物的位置。当然,没有提交的事物是不会被刷新到磁盘当中的。
fuzzy checkpoint发生的时候会记录两次LSN,也就是检查点发生的时间和检查点结束的时间。但是呢,被刷新的页在并不一定在某一个时间点是一致的,这也就是它为什么叫fuzzy的原因。较早刷入磁盘的数据可能已经修改了,较晚刷新的数据可能会有一个比前面LSN更新更小的一个LSN。fuzzy checkpoint在某种意义上可以理解为fuzzy checkpoint从redo log的第一个LSN执行到最后一个LSN。恢复以后的话,redo log就会从最后一个检查点开始时候记录的LSN开始。

MySQL停止时是否将脏数据和脏日志刷入磁盘,由变量innodb_fast_shutdown={ 0|1|2 }控制,默认值为1,即停止时忽略所有flush操作,在下次启动的时候再flush,实现fast shutdown。

LNS分析

LSN称为日志的逻辑序列号(log sequence number),在innodb存储引擎中,lsn占用8个字节。LSN的值会随着日志的写入而逐渐增大。

在 Innodb 每次都取最老的 modified page 对应的 LSN,并将此脏页的 LSN 作为 Checkpoint 点记录到日志文件,意思就是 “此 LSN 之前对应的日志和数据都已经刷新到磁盘” 。

LSN不仅存在于redo log中,还存在于数据页中,在每个数据页的头部,有一个file_page_lsn记录了当前页最终的LSN值是多少。通过数据页中的LSN值和redo log中的LSN值比较,如果页中的LSN值小于redo log中LSN值,则表示数据丢失了一部分,这时候可以通过redo log的记录来恢复到redo log中记录的LSN值时的状态。
可以通过show engine innodb status;查看引擎中的LSN

UTOOLS1569226390190.png

其中:

  • log sequence number就是当前的redo log(in buffer)中的lsn;
  • log flushed up to是当前已经写入日志文件做持久化的 LSN;
  • pages flushed up to是已经刷到磁盘数据页上的LSN;
  • last checkpoint at是上一次检查点所在位置的LSN。
innodb的LSN变化

LSN变化图.png

  1. 12点开启事务,此时4个LSN的值都是相同的为100
    log sequence number(100) = log flushed up to(100) = pages flushed up to = last checkpoint at

  2. 位置(1)执行一条 update 语句,执行完成后,buffer中的数据页和redo log都记录好更新后的LSN值101,其他值不变
    log sequence number(101) > log flushed up to(100) = pages flushed up to = last checkpoint at

  3. 位置(2)12:00:01,触发redo log的刷盘规则(0),此时redo log 文件中的LSN会更新到和buffer中一致,其他值不变
    log sequence number(101) = log flushed up to > pages flushed up to(100) = last checkpoint at

  4. 位置(3)在执行一条update语句,此时buffer中的LSN更新为102
    log sequence number(102) > log flushed up to(101) > pages flushed up to(100) = last checkpoint at

  5. 位置(4)checkpoint出现,触发数据页和日志页刷盘,但是需要一定的时间来完成,所以在数据页刷盘未完成时,检查点的LSN还是上一次检查点的LSN,但是磁盘上的数据页和日志页的LSN已经变化了。
    log sequence number(102) ? log flushed up to ? pages flushed up to > last checkpoint at

    log flushed up to 和 pages flushed up to 的大小无法确定,因为日志刷盘的速度和数据刷盘的速度无法确认。但是checkpoint机制会保护数据刷盘的速度慢于日志刷盘的速度。

  6. 位置(5)数据页和日志页输盘完毕,此时所有的LSN都等于102
    log sequence number(102) = log flushed up to = pages flushed up to = last checkpoint at

  7. 位置(6)执行一条insert语句,此时buffer中的LSN更新为103
    log sequence number(103) > log flushed up to(102) = pages flushed up to = last checkpoint at

  8. 位置(7)提交事务,提交动作会触发日志刷盘,但是不会触发数据刷盘
    log sequence number(103) = log flushed up to > pages flushed up to(102) = last checkpoint at

  9. 位置(8)checkpoint出现,这次checkpoint不会触发日志刷盘,因为日志的LSN在checkpoint出现之前就同步过了,所以4个LSN的值的情况都是103

innodb的恢复

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

重启innodb时,checkpoint 表示已经完整刷到磁盘上的LSN,因此恢复时仅需要恢复从 checkpoint 开始的日志部分。例如,数据库中上次的 checkpoint 的LSN为10000,切事务已经提交过了。启动数据库时会检查磁盘中数据页的LSN,如果数据页的LSN小于日志中的LSN,则会从检查点开始恢复。

redo log 的两阶段提交

redo log 两阶段提交.jpg

事务的提交涉及到binlog及具体的存储的引擎的事务提交。所以Innodb引擎用两阶段提交来保证的事务的完整性,在上图中可以看到,在数据页的数据修改完成后,首先进行了redo log的写入,同时将redo log的状态设置成prepare。然后进行了bin log的写入。最后提交事务的时候,将redo log的状态设置成commit

bin log是逻辑日志,记录的是这个语句的原始逻辑,并且是采用“追加写”的形式。负责mysql的归档。这是redo log没有的能力。

redo log 和 binlog 都可以用于表示事务的提交状态,redo log 用于crash-safe,bin log 用于数据归档,两阶段提交就是让这2个状态保持逻辑上的一致

如果没有采用两阶段提交:

  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,与原库的值不同。

有了两阶段提交,当系统出现异常宕机时:

  1. binlog 有记录,redo log 状态 commit: 正常完成的事务,不需要恢复
  2. binlog 有记录,redo log 状态 prepare: 在 binlog 写完提交事务之前的 crash, 恢复操作:提交事务
  3. binlog 无记录,redo log 状态 prepare: 在 binlog写完之前的 crash, 恢复操作:回滚事务
  4. binlog 无记录,redo log 无记录: 在 redo log 写之前 crash, 恢复操作:回滚事务

小结

这里大概了描述了一下innodb中redo log 的相关知识,redo log在mysql中有着很重要的地位,同时其中的一些设计也对我们的编程有帮助,比如两阶段提交。通过系统的学习也能了解到mysql在数据恢复上是如何实现的。

参考

猜你喜欢

转载自www.cnblogs.com/jinlin/p/11582085.html
今日推荐