Redo日志

Catalogue
  1. 1. undo log
  2. 2. redo log
    1. 2.1. redo参数
    2. 2.2. redo buffer
    3. 2.3. 一条redo log记录了什么
    4. 2.4. log block
    5. 2.5. log group
    6. 2.6. redo log file
    7. 2.7. 表空间
  3. 3. 参考资料

表空间:
数据页:以页的大小,记录表的数据,属于一个表空间,通过SpaceId 和 PageNum, 即表空间id和页号能确定一个数据页

undo log 用来事务回滚及MVCC的功能
redo log 用来保证事务的持久性,一个redo log记录某个数据页中某偏移量处的数据要修改成什么数据

undo log

redo log

当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,称buffer pool的数据页为dirty page 脏数据,如果这个时候发生非正常的DB服务重启,那么这些数据还在内存,并没有同步到磁盘文件中,也就是会发生数据丢失,如果这个时候,能够在有一个文件,当buffer pool 中的data page变更结束后,把相应修改记录记录到这个文件,那么当DB服务发生crash的情况,恢复DB的时候,也可以根据这个文件的记录内容,重新将修改的数据更新到磁盘数据页文件,保证数据的完整性。

这个文件就是redo log ,用于记录 数据修改后的记录,顺序记录。它可以带来这些好处:

  • 当buffer pool中的dirty page 还没有刷新到磁盘的时候,发生crash,启动服务后,可通过redo log 找到需要重新刷新到磁盘文件的记录
  • 如果将buffer pool中的数据直接flush到disk file,是一个随机IO,效率较差,而把buffer pool中的数据记录到redo log,是一个顺序IO,可以提高事务提交的速度

redo参数

  • innodb_log_files_in_group:redo log 文件的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认2个,最大100个。
  • innodb_log_file_size:文件设置大小,默认值为 48M,最大值为512G,注意最大值指的是整个 redo log系列文件之和,即(innodb_log_files_in_group变量值 * innodb_log_file_size )不能大于最大值512G。
  • innodb_log_group_home_dir:文件存放路径
  • innodb_log_buffer_size:Redo Log 缓存区,默认8M,可设置1-8M。延迟事务日志写入磁盘,把redo log 放到该缓冲区,然后根据innodb_flush_log_at_trx_commit参数的设置,再把日志从buffer中flush到磁盘中。
  • innodb_flush_log_at_trx_commit
    • innodb_flush_log_at_trx_commit=1,每次commit都会把redo log从redo log buffer写入到system,并通过fsync强制刷新到磁盘文件中。
    • innodb_flush_log_at_trx_commit=2,每次事务提交时MySQL会把日志从redo log buffer写入到system,但只写入到file system buffer,由系统内部来fsync到磁盘文件。如果数据库实例crash,不会丢失redo log,但是如果服务器crash,由于file system buffer还来不及fsync到磁盘文件,所以会丢失这一部分的数据。
    • innodb_flush_log_at_trx_commit=0,事务发生过程,日志一直记录在redo log buffer中,跟其他设置一样,但是在事务提交时,不产生redo 写操作,而是MySQL内部每秒操作一次,从redo log buffer,把数据写入到系统中去。如果发生crash,即丢失1s内的事务修改操作。由于进程调度策略问题,这个“每秒执行一次 flush(刷到磁盘)操作”并不是保证100%的“每秒”。

redo buffer

Redo log文件以ib_logfile_x_命名,Redo log 以顺序的方式写入文件文件,写满时则回溯到第一个文件,进行覆盖写。(但在做redo checkpoint时,也会更新第一个日志文件的头部checkpoint标记,所以严格来讲也不算顺序写)。

实际上redo log有两部分组成:redo log buffer跟redo log file。buffer pool中把数据修改情况记录到redo log buffer,出现以下情况,再把redo log buffer刷下到redo log file:

  1. Redo log buffer空间不足
  2. 事务提交(依赖innodb_flush_log_at_trx_commit参数设置)
  3. 后台线程
  4. 做checkpoint
  5. 实例shutdown
  6. binlog切换

一条redo log记录了什么

重做日志格式: 不同的数据库操作会有对应的重做日志格式。此外,由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。虽然有着不同的重做日志格式,但他们有着通用的头部格式,通用的头部格式由一下3部分组成

  • redo_log_type 重做日志类型
  • space: 表空间ID
  • page_no 页的偏移量
  • redo log body: 根据重做日志类型的不对,会有不同的存储内容

为了应对InnoDB各种各样不同的需求,到MySQL 8.0为止,已经有多达65种的REDO记录。用来记录这不同的信息,恢复时需要判断不同的REDO类型,来做对应的解析。根据REDO记录不同的作用对象,可以将这65中REDO划分为三个大类:作用于Page,作用于Space以及提供额外信息的Logic类型。

  1. 作用于Page的REDO: 这类REDO占所有REDO类型的绝大多数,根据作用的Page的不同类型又可以细分为,Index Page REDO,Undo Page REDO,Rtree Page REDO等。比如MLOG_REC_INSERT,MLOG_REC_UPDATE_IN_PLACE,MLOG_REC_DELETE三种类型分别对应于Page中记录的插入,修改以及删除。这里还是以MLOG_REC_UPDATE_IN_PLACE为例来看看其中具体的内容:其中,Type就是MLOG_REC_UPDATE_IN_PLACE类型,Space ID 和 Page Number唯一标识一个Page页,这三项是所有REDO记录都需要有的头信息,后面的是MLOG_REC_UPDATE_IN_PLACE类型独有的,其中Record Offset用给出要修改的记录在Page中的位置偏移,Update Field Count说明记录里有几个Field要修改,紧接着对每个Field给出了Field编号(Field Number),数据长度(Field Data Length)以及数据(Filed Data)。
  2. 作用于Space的REDO:这类REDO针对一个Space文件的修改,如MLOG_FILE_CREATE,MLOG_FILE_DELETE,MLOG_FILE_RENAME分别对应对一个Space的创建,删除以及重命名。由于文件操作的REDO是在文件操作结束后才记录的,因此在恢复的过程中看到这类日志时,说明文件操作已经成功,因此在恢复过程中大多只是做对文件状态的检查,以MLOG_FILE_CREATE来看看其中记录的内容:同样的前三个字段还是Type,Space ID和Page Number,由于是针对Space的操作,这里的Page Number永远是0。在此之后记录了创建的文件flag以及文件名,用作重启恢复时的检查。
  3. 提供额外信息的Logic REDO: 除了上述类型外,还有少数的几个REDO类型不涉及具体的数据修改,只是为了记录一些需要的信息,比如最常见的MLOG_MULTI_REC_END就是为了标识一个REDO组,也就是一个完整的原子操作的结束。

log block

前面说明了在InnoDB存储引擎中一条redo log记录了什么,现在说一下它是如何进行物理存储的管理。

首先一条redo log内容大小是不确定的,但是在写入磁盘时是按固定大小512byte的log block写入的,如果redo log较大会占用多个log block, 较小时一个log block会存有多个redo log。另外redo log buffer、redo log file都是以log block的方式进行保存的。

一个log block块除了存日志本身之外,还由日志块头(log block header)及日志块尾(log block tailer)两部分组成。重做日志头一共占用12字节,重做日志尾占用8字节。故每个重做日志块实际可以存储的大小为492字节(512-12-8),如图显示重做日志块缓存的结构:

如图显示了重做日志缓存的结果,可以发现,重做日志缓存由每个为512字节大小的日志块所组成,日志块由三部分组成,依次为日志块头(log block header)、日志内容(log body)、日志块尾(log block tailer)。log block header由4部分组成,分别占4、2、2、4字节,共12字节。

  • LOG_BLOCK_HDR_NO:log buffer 是由log block组成,在内部log buffer就好似一个数组,因此LOG_BLOCK_HDR_NO用来标记这个数组中的位置,尤其是递增并且循环使用的。占用4个字节。但是由于第一位用来判断是否是flush bit,所以最大值为2G(2的31次方)
  • LOG_BLOCK_HDR_DATA_LEN:占用2个字节,表示log block所占用的大小,当log block被写满时,该值为0x200,表示使用全部的log block空间,即占用512字节
  • LOG_BLOCK_FIRST_REC_GROUP:占用2个字节,表示log block中第一个日志所在的偏移量,因为存在该log block开头保存有上一个日志剩余数据的情况。如果该值的大小和LOG_BLOCK_HDR_DATA_LEN相同,则表示当前log block不包含新的日志,只保存了之前日志剩余数据的情况。如事务T1的重做日志1占用762字节,事务T2的重做日志占用100字节。由于每个log block实际只能保存492字节,因此其在log buffer的情况应该如图所示:

从图可以观察到,由于事务T1的重做日志占用792字节,因此需要占用两个log block。左侧的log block中 LOG_BLOCK_FIRST_REC_GROUP 为 12,即前12个字节是log block header, 然后第一个存储日志的开始位置,在第二个log block中,由于包含了之前事务T1的重做日志,事务T2的日志才是log block中第一日志,因此该log block的LOG_BLOCK_FIRST_REC_GROUP为(270+12)

  • LOG_BLOCK_CHECKPOINT_NO占用4字节,表示该log block最后被写入时的检查点,写Block时的next_checkpoint_number,用来发现文件的循环使用
  • log block tailer 只由1个部分组成,且值和LOG_BLOCK_HDR_NO相同,并在函数log_block_init中被初始化 LOG_BLOCK_TRL_NO 大小为4字节

log group

log group 重做日志组,是一个逻辑的概念,并没有一个实际的物理文件来表示log group信息,log group 由多个重做日志文件组成。每个log group中的日志文件是相同的。

InnoDB存储引擎默认只能有一个log group。

redo log file

重做日志文件中存储就是之前log buffer中保存的log block。因此其也是根据块的方式进行物理存储的管理,每个块的大小与log block一样,同样为512字节,在InnoDB存储引擎运行过程中,log buffer根据一定的规则将内存中的log block刷新到磁盘。这个规则具体是

  • 事务提交时
  • 当log buffer中有一半的内存空间已经被使用时
  • log checkpoint时

对于log block的写入追加在redo log file最后部分,当一个redo log file写满时,会接着写下一个redo log file,其使用的方式为round-robin。

虽然log block总是在redo log file的最后部分进行写入,有的读者可能以为对redo log file的写入时顺序的,其实不是,因为redo log file除了保存log buffer刷新到磁盘的log block,还保存了一些其他的信息,这些信息一共占用2KB大小,即每个redo log file的前2KB的部分不保存log block信息,对于log group中的第一个redo log file,其前2KB的部分保存4个512字节大小的块,其中存放的内容为

需要特别注意,上述信息仅在每个log group的第一个redo log file中进行存储,log group中的其余redo log file仅保留这些空间,但不保存上述信息。正因为保存了这些信息,就意味着对redo log file 的写入并不是完全顺序的。因为其除了log block的写入操作,还需要更新前2KB部分的信息,这些信息对于InnoDB存储引擎的恢复操作来说非常关键和重要,故log group与redo log file 之间的关系如下

在log filer header 后面的部分为InnoDB存储引擎保存的checkpoint(检查点)值,其设计是交替写入。这样的设计避免了因介质失败而导致无法找到可用的checkpoint的情况

表空间

在innodb存储引擎中数据是按照表空间来组织存储的,即表空间是表空间文件,是实际存在的物理文件。

sys表空间:默认情况下,MySQL会初始化一个大小为12MB,名为ibdata1文件,并且随着数据的增多,它会自动扩容。这个ibdata1文件是系统表空间,也是默认的表空间,也是默认的表空间物理文件,也是传说中的共享表空间。

file per table 表空间:如果你想让每一个数据库表都有一个单独的表空间文件的话,可以通过参数innodb_file_per_table设置。这个参数只有在MySQL5.6或者是更高的版本中才可以使用。独立的表空间文件命名规则:表名.ibd。注意:独立表空间文件中仅存放该表对应数据、索引、insert buffer bitmap。其余的诸如:undo信息、insert buffer 索引页、double write buffer 等信息依然放在默认表空间,也就是共享表空间中。

1
2
3
4
5
6
# my.cnf   
#配置sys表空间
innodb_data_file_path=/dir1/ibdata1:2000M;/dir2/ibdata2:2000M:autoextend

# file per table 表空间
innodb_file_per_table=ON

最后再简述一下这种file per table的优缺点:

优点:

  • 提升容错率,表A的表空间损坏后,其他表空间不会收到影响。s
  • 使用MySQL Enterprise Backup快速备份或还原在每表文件表空间中创建的表,不会中断其他InnoDB 表的使用

缺点:对fsync系统调用来说不友好,如果使用一个表空间文件的话单次系统调用可以完成数据的落盘,但是如果你将表空间文件拆分成多个。原来的一次fsync可能会就变成针对涉及到的所有表空间文件分别执行一次fsync,增加fsync的次数。

临时表空间:临时表空间用于存放用户创建的临时表和磁盘内部临时表。参数innodb_temp_data_file_path定义了临时表空间的一些名称、大小、规格属性
独享表空间:独享表空间存储方式使用“.ibd”文件来存放数据,且每个表一个“.ibd”文件,文件存放在和MyISAM数据相同的位置。
共享表空间:如果选用共享存储表空间来存放数据,则会使用 ibdata 文件来存放,所有表共同使用一个(或者多个,可自行配置)ibdata文件。
undo表空间:相信你肯定听过说undolog,常见的当你的程序想要将事物rollback时,底层MySQL其实就是通过这些undo信息帮你回滚的。在MySQL的设定中,有一个表空间可以专门用来存放undolog的日志文件。然而,默认配置是会将undolog放置到系统表空间中。如果你的MySQL是新安装的,那你可以通过查看变量:innodb_undo_tablespaces,看看你的MySQL undo表空间的使用情况。

那undo log到底是该使用默认的配置放在系统表空间呢?还是该放在undo表空间呢?
这其实取决服务器使用的存储卷的类型。
如果是SSD存储,那推荐将undo info存放在 undo表空间中。

参考资料