所在的位置: mysql >> mysql资源 >> 庖丁解InnoDB之REDOLOG

庖丁解InnoDB之REDOLOG

数据库故障恢复机制的前世今生一文中提到,今生磁盘数据库为了在保证数据库的原子性(A,Atomic)和持久性(D,Durability)的同时,还能以灵活的刷盘策略来充分利用磁盘顺序写的性能,会记录REDO和UNDO日志,即ARIES方法。本文将重点介绍REDOLOG的作用,记录的内容,组织结构,写入方式等内容,希望读者能够更全面准确的理解REDOLOG在InnoDB中的位置。本文基于MySQL8.0代码。

一为什么需要记录REDO

为了取得更好的读写性能,InnoDB会将数据缓存在内存中(InnoDBBuffrPool),对磁盘数据的修改也会落后于内存,这时如果进程或机器崩溃,会导致内存数据丢失,为了保证数据库本身的一致性和持久性,InnoDB维护了REDOLOG。修改Pag之前需要先将修改的内容记录到REDO中,并保证REDOLOG早于对应的Pag落盘,也就是常说的WAL,WritAhadLog。当故障发生导致内存数据丢失后,InnoDB会在重启时,通过重放REDO,将Pag恢复到崩溃前的状态。

二需要什么样的REDO

那么我们需要什么样的REDO呢?首先,REDO的维护增加了一份写盘数据,同时为了保证数据正确,事务只有在他的REDO全部落盘才能返回用户成功,REDO的写盘时间会直接影响系统吞吐,显而易见,REDO的数据量要尽量少。其次,系统崩溃总是发生在始料未及的时候,当重启重放REDO时,系统并不知道哪些REDO对应的Pag已经落盘,因此REDO的重放必须可重入,即REDO操作要保证幂等。最后,为了便于通过并发重放的方式加快重启恢复速度,REDO应该是基于Pag的,即一个REDO只涉及一个Pag的修改。

熟悉的读者会发现,数据量小是LogicalLogging的优点,而幂等以及基于Pag正是PhysicalLogging的优点,因此InnoDB采取了一种称为PhysiologicalLogging的方式,来兼得二者的优势。所谓PhysiologicalLogging,就是以Pag为单位,但在Pag内以逻辑的方式记录。举个例子,MLOG_REC_UPDATE_IN_PLACE类型的REDO中记录了对Pag中一个Rcord的修改,方法如下:

(PagID,RcordOffst,(Fild,Valu)...(Fildi,Valui)...)

其中,PagID指定要操作的Pag页,RcordOffst记录了Rcord在Pag内的偏移位置,后面的Fild数组,记录了需要修改的Fild以及修改后的Valu。

由于PhysiologicalLogging的方式采用了物理Pag中的逻辑记法,导致两个问题:

、需要基于正确的Pag状态上重放REDO

由于在一个Pag内,REDO是以逻辑的方式记录了前后两次的修改,因此重放REDO必须基于正确的Pag状态。然而InnoDB默认的Pag大小是6KB,是大于文件系统能保证原子的KB大小的,因此可能出现Pag内容成功一半的情况。InnoDB中采用了DoublWritBuffr的方式来通过写两次的方式保证恢复的时候找到一个正确的Pag状态。这部分会在之后介绍BuffrPool的时候详细介绍。

2、需要保证REDO重放的幂等

DoublWritBuffr能够保证找到一个正确的Pag状态,我们还需要知道这个状态对应REDO上的哪个记录,来避免对Pag的重复修改。为此,InnoDB给每个REDO记录一个全局唯一递增的标号LSN(LogSquncNumbr)。Pag在修改时,会将对应的REDO记录的LSN记录在Pag上(FIL_PAGE_LSN字段),这样恢复重放REDO时,就可以来判断跳过已经应用的REDO,从而实现重放的幂等。

三REDO中记录了什么内容

知道了InnoDB中记录REDO的方式,那么REDO里具体会记录哪些内容呢?为了应对InnoDB各种各样不同的需求,到MySQL8.0为止,已经有多达65种的REDO记录。用来记录这不同的信息,恢复时需要判断不同的REDO类型,来做对应的解析。根据REDO记录不同的作用对象,可以将这65中REDO划分为三个大类:作用于Pag,作用于Spac以及提供额外信息的Logic类型。

、作用于Pag的REDO

这类REDO占所有REDO类型的绝大多数,根据作用的Pag的不同类型又可以细分为,IndxPagREDO,UndoPagREDO,RtPagREDO等。比如MLOG_REC_INSERT,MLOG_REC_UPDATE_IN_PLACE,MLOG_REC_DELETE三种类型分别对应于Pag中记录的插入,修改以及删除。这里还是以MLOG_REC_UPDATE_IN_PLACE为例来看看其中具体的内容:

其中,Typ就是MLOG_REC_UPDATE_IN_PLACE类型,SpacID和PagNumbr唯一标识一个Pag页,这三项是所有REDO记录都需要有的头信息,后面的是MLOG_REC_UPDATE_IN_PLACE类型独有的,其中RcordOffst用给出要修改的记录在Pag中的位置偏移,UpdatFildCount说明记录里有几个Fild要修改,紧接着对每个Fild给出了Fild编号(FildNumbr),数据长度(FildDataLngth)以及数据(FildData)。

2、作用于Spac的REDO

这类REDO针对一个Spac文件的修改,如MLOG_FILE_CREATE,MLOG_FILE_DELETE,MLOG_FILE_RENAME分别对应对一个Spac的创建,删除以及重命名。由于文件操作的REDO是在文件操作结束后才记录的,因此在恢复的过程中看到这类日志时,说明文件操作已经成功,因此在恢复过程中大多只是做对文件状态的检查,以MLOG_FILE_CREATE来看看其中记录的内容:

同样的前三个字段还是Typ,SpacID和PagNumbr,由于是针对Pag的操作,这里的PagNumbr永远是0。在此之后记录了创建的文件flag以及文件名,用作重启恢复时的检查。

3、提供额外信息的LogicREDO

除了上述类型外,还有少数的几个REDO类型不涉及具体的数据修改,只是为了记录一些需要的信息,比如最常见的MLOG_MULTI_REC_END就是为了标识一个REDO组,也就是一个完整的原子操作的结束。

、REDO是如何组织的

所谓REDO的组织方式,就是如何把需要的REDO内容记录到磁盘文件中,以方便高效的REDO写入,读取,恢复以及清理。我们这里把REDO从上到下分为三层:逻辑REDO层、物理REDO层和文件层。

逻辑REDO层

这一层是真正的REDO内容,REDO由多个不同Typ的多个REDO记录收尾相连组成,有全局唯一的递增的偏移sn,InnoDB会在全局log_sys中维护当前sn的最大值,并在每次写入数据时将sn增加REDO内容长度。如下图所示:

2物理REDO层

磁盘是块设备,InnoDB中也用Block的概念来读写数据,一个Block的长度OS_FILE_LOG_BLOCK_SIZE等于磁盘扇区的大小52B,每次IO读写的最小单位都是一个Block。除了REDO数据以外,Block中还需要一些额外的信息,下图所示一个LogBlock的的组成,包括2字节的BlockHadr:前字节中FlushFlag占用最高位bit,标识一次IO的第一个Block,剩下的3个个bit是Block编号;之后是2字节的数据长度,取值在[2,];紧接着2字节的FirstRcordOffst用来指向Block中第一个REDO组的开始,这个值的存在使得我们对任何一个Block都可以找到一个合法的的REDO开始位置;最后的字节ChckpointNumbr记录写Block时的nxt_chckpoint_numbr,用来发现文件的循环使用,这个会在文件层详细讲解。Block末尾是字节的BlockTailr,记录当前Block的Chcksum,通过这个值,读取Log时可以明确Block数据有没有被完整写完。

Block中剩余的中间98个字节就是REDO真正内容的存放位置,也就是我们上面说的逻辑REDO。我们现在将逻辑REDO放到物理REDO空间中,由于Block内的空间固定,而REDO长度不定,因此可能一个Block中有多个REDO,也可能一个REDO被拆分到多个Block中,如下图所示,棕色和红色分别代表BlockHadr和Tailr,中间的REDO记录由于前一个Block剩余空间不足,而被拆分在连续的两个Block中。

由于增加了BlockHadr和Tailr的字节开销,在物理REDO空间中用LSN来标识偏移,可以看出LSN和SN之间有简单的换算关系:

constxprinlinlsn_tlog_translat_sn_to_lsn(lsn_tsn){turn(sn/LOG_BLOCK_DATA_SIZE*OS_FILE_LOG_BLOCK_SIZE+sn%LOG_BLOCK_DATA_SIZE+LOG_BLOCK_HDR_SIZE);}

SN加上之前所有的Block的Hadr以及Tailr的长度就可以换算到对应的LSN,反之亦然。

3文件层

最终REDO会被写入到REDO日志文件中,以ib_logfil0、ib_logfil...命名,为了避免创建文件及初始化空间带来的开销,InooDB的REDO文件会循环使用,通过参数innodb_log_fils_in_group可以指定REDO文件的个数。多个文件收尾相连顺序写入REDO内容。每个文件以Block为单位划分,每个文件的开头固定预留个Block来记录一些额外的信息,其中第一个Block称为HadrBlock,之后的3个Block在0号文件上用来存储Chckpoint信息,而在其他文件上留空:

其中第一个HadrBlock的数据区域记录了一些文件信息,如下图所示,字节的Format字段记录Log的版本,不同版本的LOG,会有REDO类型的增减,这个信息是8.0开始才加入的;8字节的StartLSN标识当前文件开始LSN,通过这个信息可以将文件的offst与对应的lsn对应起来;最后是最长32位的Cator信息,正常情况下会记录MySQL的版本。

现在我们将REDO放到文件空间中,如下图所示,逻辑REDO是真正需要的数据,用sn索引,逻辑REDO按固定大小的Block组织,并添加Block的头尾信息形成物理REDO,以lsn索引,这些Block又会放到循环使用的文件空间中的某一位置,文件中用offst索引:

虽然通过LSN可以唯一标识一个REDO位置,但最终对REDO的读写还需要转换到对文件的读写IO,这个时候就需要表示文件空间的offst,他们之间的换算方式如下:

constautoal_offst=log.curnt_fil_al_offst+(lsn-log.curnt_fil_lsn);

切换文件时会在内存中更新当前文件开头的文件offst,curnt_fil_al_offst,以及对应的LSN,curnt_fil_lsn,通过这两个值可以方便地用上面的方式将LSN转化为文件offst。注意这里的offst是相当于整个REDO文件空间而言的,由于InnoDB中读写文件的spac层实现支持多个文件,因此,可以将首位相连的多个REDO文件看成一个大文件,那么这里的offst就是这个大文件中的偏移。

五如何高效地写REDO

作为维护数据库正确性的重要信息,REDO日志必须在事务提交前保证落盘,否则一旦断电将会有数据丢失的可能,因此从REDO生成到最终落盘的完整过程成为数据库写入的关键路径,其效率也直接决定了数据库的写入性能。这个过程包括REDO内容的产生,REDO写入InnoDBLogBuffr,从InnoDBLogBuffr写入操作系统PagCach,以及REDO刷盘,之后还需要唤醒等待的用户线程完成Commit。下面就通过这几个阶段来看看InnoDB如何在高并发的情况下还能高效地完成写REDO。

REDO产生

我们知道事务在写入数据的时候会产生REDO,一次原子的操作可能会包含多条REDO记录,这些REDO可能是访问同一Pag的不同位置,也可能是访问不同的Pag(如Bt节点分裂)。InnoDB有一套完整的机制来保证涉及一次原子操作的多条REDO记录原子,即恢复的时候要么全部重放,要不全部不重放,这部分将在之后介绍恢复逻辑的时候详细介绍,本文只涉及其中最基本的要求,就是这些REDO必须连续。InnoDB中通过min-transaction实现,简称mtr,需要原子操作时,调用mtr_start生成一个mtr,mtr中会维护一个动态增长的m_log,这是一个动态分配的内存空间,将这个原子操作需要写的所有REDO先写到这个m_log中,当原子操作结束后,调用mtr_


转载请注明:http://www.aierlanlan.com/rzgz/5201.html

  • 上一篇文章:
  •   
  • 下一篇文章: 没有了