MySQL阅读笔记——14.redo日志

14.1 问题的提出

  对数据库的操作都是以 为单位加载到 buffer pool操作的,防止故障引起数据丢失,保证 持久性 如果每次提交事务都将页面刷到磁盘,会有两个问题。一是事务操作的页面可能并不相邻,随机I/O。二是InnoDB以页为单位进行磁盘I/O,即使只修改一个字段。只需要把修改的数据记录一下,保证系统故障后可以恢复即可 ,因此提出了redo日志的概念

redo日志占用空间很小,存储表空间id、页号、偏移量和需要更新的值,并且redo日志是顺序I/O,组提交降低了IOPS

Undo日志是InnoDB引擎特有日志,而MyISAM没有故障恢复能力,依赖Server层的binlog日志(所有引擎都可以使用),redo日志是循环写,用于故障恢复,一定大小后会覆盖之前日志;binlog是顺序写,用于数据库的增量恢复(归档) 事务操作写入redo日志后会进入 prepare 阶段,之后会写入binlog日志,提交事务后会进入 commit,这就是 两阶段提交 两阶段提交 是为了让故障恢复的数据 和 全量+增量恢复的是护具一致(即保证redo日志记录的操作和binlog记录的操作一致)

binlog有两种模式:statement模式记录sql,row格式一次改动记录两条内容,一条改动前一条改动后。一般采用row格式

14.2 redo日志结构

 

属性说明
type 日志类型
space ID 表空间ID
page number 页号
data redo具体内容

  redo日志根据不同type划分为不同的类型,其中有一些只是指明在某个偏移量修改几个字节的值,以及具体被修改的内容,叫做 物理日志 ,还有一些redo日志除了指明对哪个表空间哪个页哪个位置进行修改的物理意义,还需要在处理的时候调用一些函数,等逻辑层面处理,如:insert操作可能要插入数据,修改页头,更新槽信息等一系列操作

14.3 redo日志写入

  写入redo日志不是直接直接写道磁盘,而是向操作系统申请了一片连续内存区域叫redo log buffer(通过innodb_log_buffer_size启动参数设置大小,在MySQL5.7中默认16MB),向log buffer写入redo日志是顺序写,通过全局变量buf_free指明redo日志写入到log buffer哪个位置

  一次原子性访问称为Mini-Transaction,简称mtr,一个mtr产生一条或者一组redo日志,一组redo日志以MLOG_MULTI_REC_END类型的redo日志结尾。(如果mrt只产生一条redo日志,则不会以MLOG_MULTI_REC_END结尾,而会将type属性的第一个字节设置为1,代表该需要保证原子性的操作只产生了单一的一条redo日志) 将mtr生成的redo日志存放再512字节的页中,这种页称为block,内存中是以这种页面形式存储,为防止操作丢失将redo日志刷盘数据也是以block页面形式存储到文件组ib_logfile[n]中(ib_logfile[n]文件组总大小512G,总共能存储1G大小的block页)

LOG_BLOCK_HDR_NO = ((lsn / 512) & 0x3FFFFFFFUL) + 1其中0x3FFFFFFFUL代表1GB,也就是说系统最多能产生不重复的LOG_BLOCK_HDR_NO值只有1GB 另外,LOG_BLOCK_HDR_NO值的第一个比特位比较特殊,称之为flush bit,如果该值为1,代表着本block是在某次将log buffer中的block刷新到磁盘的操作中的第一个被刷入的block。

每一个mtr执行过程可能产生若干条redo日志,mtr运行过程产生日志并没有直接插入到log buffer而是先暂存到一个地方,当mtr结束时再将产生的一组redo日志复制到log buffer中,不同事务并发执行,不同事物之间的mtr也可能会交替执行(交替写入log buffer

14.4 redo刷盘

mtr生成的redo日志在一定条件下会刷新到磁盘保存,默认保存在MySQL数据目录下的ib_logfile0ib_logfile1的文件中(通过show variables like 'datadir'命令查看),一个写满了就向另一个文件写入,如果全都写满了则重新转到ib_logfile0进行覆写

事务未提交情况下也会刷盘:

  1. MySQL后台线程每隔一秒就会把redo log buffer中的日志写入到页缓存

  2. redo log buffer 占用innodb_log_buffer_size的一半

  3. 并行事务提交,顺带刷盘

通过启动参数可以调节ib_logfile innodb_log_group_home_dir:指定redo日志文件所在目录 innodb_log_file_size:指定每个redo日志文件大小(MySQL5.7默认48MB) innodb_log_files_in_group:redo日志文件个数(默认为2,最大100)

redo日志文件最大值=innodb_log_file_size × innodb_log_files_in_group

14.4.1 redo日志文件格式

  redo日志文件由若干的512字节的block组成(block就是存储redo的 ),redo日志文件组中每个文件大小、格式都一样(前2048字节,即前4个block存储一些管理信息,后面的字节存储log buffer中的镜像)

属性名长度(单位:字节)描述
LOG_HEADER_FORMAT 4 redo日志的版本,在MySQL 5.7.21中该值永远为1
LOG_HEADER_PAD1 4 做字节填充用的,没什么实际意义,忽略~
LOG_HEADER_START_LSN 8 标记本redo日志文件开始的LSN值,也就是文件偏移量为2048字节初对应的LSN值(关于什么是LSN我们稍后再看哈,看不懂的先忽略)。
LOG_HEADER_CREATOR 32 一个字符串,标记本redo日志文件的创建者是谁。正常运行时该值为MySQL的版本号,比如:"MySQL 5.7.21",使用mysqlbackup命令创建的redo日志文件的该值为"ibbackup"和创建时间。
LOG_BLOCK_CHECKSUM 4 本block的校验值,所有block都有,我们不关心

14.4.2 Log Sequence Number(LSN)

  redo日志不断递增,不会缩减,因此MySQL通过LSN记录redo日志写入到log buffer量(初始值为8704),每一组mtr生成redo日志都有唯一的LSN与之对应,LSN越小说明redo日志产生越早,redo日志先被写入到log buffer中,之后才逐步刷新到磁盘的ib_logfile[n]文件中,因此使用了flushed_to_disk_lsn全局变量标记log buffer中哪些日志被刷新到磁盘,当有新的redo日志写入到log buffer时,lsn会增长,但flushed_to_disk_lsn不变,随着log buffer不断刷到磁盘,flushed_to_disk_lsn也随之增长,直到两者相同,此时log buffer中所有的redo日志都刷新到磁盘ib_logfile[n]文件中

向磁盘写入文件是先写入到操作系统缓冲区中,如果需要将缓冲区数据写到磁盘,需要调用操作系统提供的 fsync函数,只有当系统执行 fsync函数 将数据从缓冲区刷新到磁盘flushed_to_disk_lsn才会随之增长,如果只是刷新到缓冲区则write_lsn增长而flushed_to_disk_lsn不会增长

  mtr结束,会把对应的一组redo日志写入到log buffer,之后将mtr过程中可能修改的页面(缓存页)加入到Buffer Pool的flush链表,并且修改并且修改对应控制块的oldest_modificationnewest_modification(如果是第一次缓存该页面,则会把它的控制块加入到flush链表头部,后续修改不会重新插入。即:flush链表中脏页是按照页面第一次修改时间从大到小排序)

oldest_modification:某页面加载到Bufffer Pool后第一次修改,修改该mtr开始时对应的lsn值

newest_modification:每修改一次该页面,mrt结束时对应的lsn值,即,该页面最后一次修改的lsn值

14.4.3 checkpoint

  redo日志文件组(ib_logfile[n])容量有限,当其中的redo日志对应的脏页已经刷新到磁盘则它占用的磁盘空间可以覆盖(注意是脏页数据刷盘,而只是redo日志刷盘),使用checkpoint_lsn全局变量代表log buffer可以被覆盖的redo日志量(初是8704)

增加checkpoint_lsn的操作称为一次 checkpoint,具体的 checkpoint_lsn 步骤如下:

  1. 获取buffer pool中最早被修改的的脏页对应的oldest_modification,将该值赋给checkpoint_lsn(小于该值均可被覆盖)

  2. checkpoint_lsn和redo日志文件组偏移量以及checkpoint_no写入到ib_logfile[n]文件的前四个block中的checkpoint1或者checkpoint2(只会写到日志文件组的第一个日志文件中,checkpoint_no为偶数写入到checkpoint1,为奇数写入到checkpoint2

show engine innodb status \G
Log sequence number 124476971 #系统lsn值,也就是写入log buffer的redo量
Log flushed up to   124099769 #系统写入磁盘的日志量
Pages flushed up to 124052503 #flush链表中最早被修改页面的对应的oldest_modification
Last checkpoint at  124052494 #当前checkpoint_lsn
0 pending log flushes, 0 pending chkp writes
24 log i/o's done, 2.00 log i/o's/second

通过innodb_flush_log_at_trx_commit可以修改事务持久性

0:提交事务不立即向磁盘同步redo日志,而是交给后台线程做

1:提交事务立即将redo日志刷盘,保证事务持久性,同时也是 默认值

2:提交事务redo日志写到操作系统缓冲区,但并不保证刷新到磁盘

14.5 MySQL奔溃恢复

  ib_logfile[n]文件组第一个文件中的前4个block页存储管理信息,其中两个block页分别存储checkpoint1checkpoint2checkpoint_no为偶数写入到checkpoint1,为奇数写入到checkpoint2),从这两个位置获取最新的checkpoint_lsn信息(值大的)即得到了 恢复的起点,每个block页面的log block header部分的LOG_BLOCK_HDR_DATA_LEN属性记录了当前block页面中使用了多少空间(初始为12,全部占用为512),如果该block页面没有被占满(即LOG_BLOCK_HDR_DATA_LEN值不是512)则该值就是 恢复的终点 即此次恢复扫描的最后一个block页面

checkpoint_lsn之前的记录都被刷新到磁盘,不需要恢复,checkpoint_lsn之后的记录可能被刷到磁盘,也可能没有。需要确定checkpoint_lsn之后到flushed_to_disk_lsn要恢复的数据,主要是因为在最近做的一次checkpoint后,可能后台线程又不断的从LRU链表flush链表中将一些脏页刷出Buffer Pool。这些在checkpoint_lsn之后的redo日志,如果它们对应的脏页在崩溃发生时已经刷新到磁盘,那在恢复时也就没有必要根据redo日志的内容修改该页面了。 具体做法是对数据页恢复过程中查找File Header部分的FIL_PAGE_LSN属性,该属性记载了最近一次修改页面时对应的lsn值(其实就是页面控制块中的newest_modification值)如果在做了某次checkpoint之后有脏页被刷新到磁盘中,那么该页对应的FIL_PAGE_LSN代表的lsn值肯定大于checkpoint_lsn的值,凡是符合这种情况的页面就不需要重复执行lsn值小于FIL_PAGE_LSN的redo日志了,所以更进一步提升了崩溃恢复的速度。

  一个事务提交前需要两次刷盘,一次redo log,一次binlog

posted @ 2020-10-08 23:57  摩诃、  阅读(215)  评论(0编辑  收藏  举报