redo日志(上)
# redo日志
背景: 因为事务提交后,数据不会立刻刷入磁盘(1.刷新一个完整的数据页太浪费了2.随机IO刷起来比较慢),而是在合适的时机刷入磁盘,但是为了不让宕机等因素将缓存数据清空,所以需要记录一份事务提交的数据,方便以后还原。
作用: 记录下记录修改的信息(所在表空间,页号,修改信息),系统崩溃了的话,也可以利用redo日志恢复。
过程: 该事务执行过程中产生的 redo 日志刷新到磁盘。
通用组成:
- type :该条 redo 日志的类型。在 MySQL 5.7.21 这个版本中,设计 InnoDB 的大叔一共为 redo 日志设计了53种不同的类型,稍后会详细介绍不同类型的 redo 日志。
- space ID :表空间ID。
- page number :页号。
- data :该条 redo 日志的具体内容。
类型:根据记录修改的字节(type字段长度)大小分类
- MLOG_1BYTE ( type 字段对应的十进制数字为 1 ):表示在页面的某个偏移量处写入1个字节的 redo 日志类型。
- MLOG_2BYTE ( type 字段对应的十进制数字为 2 ):表示在页面的某个偏移量处写入2个字节的 redo 日志类型。
- MLOG_4BYTE ( type 字段对应的十进制数字为 4 ):表示在页面的某个偏移量处写入4个字节的 redo 日志类型。
- MLOG_8BYTE ( type 字段对应的十进制数字为 8 ):表示在页面的某个偏移量处写入8个字节的 redo 日志类型。
- MLOG_WRITE_STRING ( type 字段对应的十进制数字为 30 ):表示在页面的某个偏移量处写入一串数据。
# 复杂 redo 日志类型
如果数据频繁的改动,那是不是对应的redo log也要跟着增加呢,这样岂不是和保存的数据是一个道理了,占用着和她差不多的空间,为了减少空间的消耗,提出了复杂 redo 日志类型
。
比如,MLOG_COMP_LIST_START_DELETE(范围删除的开始)和MLOG_COMP_LIST_END_DELETE(范围删除的结束)类型的redo日志
当我们要删除id=1 到id=15之间的日志,按照之前的redo日志的记录方式,是不是要保存15条对应记录的修改,而现在我们可以使用两条redo日志,分别记录范围删除的开头和结尾,这样就大大缩减了redo日志的条数。
执行DDL语句时,存储引擎做的操作有很多,比如插入一条数据,数据页中的page header,page directory要修改,如果页超过容量了,页还需要分裂,目录页和数据页之间的指针也要修改,等等操作。所以我们可以知道把一条记录插入到一个页面时需要更改的地方非常多
。看下类型为 MLOG_COMP_REC_INSERT
的 redo 日志的结构(有印象就行)
记住:redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。
保证写入redo日志的原子性,特别是插入操作会引起多条redo日志的插入
# Mini-Transaction的概念
一次记录的增删改操作,都会伴随着多条redo日志插入,为了更有效率的插入redo日志,InnoDB的大叔将多条redo日志分为多个分组,每个分组都是不可分割的
。
如何确定每个分组是否完整写入成功呢
在每组redo日志中的最后一条 redo 日志后边加上一条特殊类型的 redo 日志,该类型名称为 MLOG_MULTI_REC_END
这样在系统奔溃重启进行恢复时,只有当解析到类型为 MLOG_MULTI_REC_END
的 redo 日志,才认为解析到了一组完整的 redo 日志,才会进行恢复。否则的话直接放弃前边解析到的 redo 日志。(很像网络传输包,判断结束标志位)
# 事务的全貌
# redo log 写入过程
为了让redo log
更有效率地写入磁盘,mysql服务器会为其开辟一片连续的buffer pool
,先写入buffer pool
再找合适时机将缓存的数据写入磁盘。
我们可以通过启动参数 innodb_log_buffer_size 来指定 log buffer 的大小,在 MySQL 5.7.21 这个版本中,该启动参数的默认值为 16MB 。
# redo log block
buffer pool 是由多个 redo log block
组成
# redo log block组成部分
log block body:真正的 redo 日志都是存储到占用 496 字节大小的 log block body 中,
log block header:存储的是一些管理信息。
log block trailer :LOG_BLOCK_CHECKSUM属性的意思如下:表示block的校验值,用于正确性校验,我们暂时不关心它。
# redo日志写入log buffer
InnoDB 的大叔特意提供了一个称之为 buf_free
的全局变量,该变量指明后续写入的 redo 日志应该写入到 log buffer
中的哪个位置
我们前边说过一个 mtr 执行过程中可能产生若干条 redo 日志,这些 redo 日志是一个不可分割的组,所以其实并不是每生成一条 redo 日志,就将其插入到 log buffer 中,而是每个 mtr 运行过程中产生的日志先暂时存到一个地方,当该 mtr 结束的时候,将过程中产生的一组 redo 日志再全部复制到 log buffer 中。我们现在假设有两个名为 T1 、 T2 的事务,每个事务都包含2个 mtr ,我们给这几个 mtr 命名一下:
- 事务 T1 的两个 mtr 分别称为 mtr_T1_1 和 mtr_T1_2 。
- 事务 T2 的两个 mtr 分别称为 mtr_T2_1 和 mtr_T2_2 。
不同的事务可能是并发执行的,所以 T1 、 T2 之间的 mtr 可能是交替执行的。每当一个 mtr 执行完成时,伴随该 mtr 生成的一组 redo 日志就需要被复制到 log buffer 中,也就是说不同事务的 mtr 可能是交替写入 log buffer 的,我们画个示意图(为了美观,我们把一个 mtr 中产生的所有的 redo 日志当作一个整体来画):
从示意图中我们可以看出来,不同的 mtr 产生的一组 redo 日志占用的存储空间可能不一样,有的 mtr 产生的redo 日志量很少,比如 mtr_t1_1 、 mtr_t2_1 就被放到同一个block中存储,有的 mtr 产生的 redo 日志量非常大,比如 mtr_t1_2 产生的 redo 日志甚至占用了3个block来存储。