目录
- 一、redo日志的基本介绍
- 1.1 什么是redo日志
- 1.2 redo日志的格式
- 1.3 redo日志的类型
- 1.4 Mini-Transaction
- 二、redo日志的写入过程
- 2.1 redo log block
- 2.2 redo日志缓冲区
- 2.3 将redo日志写入log buffer
- 2.3 redo日志刷新到磁盘
- 2.5 redo日志文件组
- 2.6 redo日志文件格式
- 2.7 log sequence number
- 2.8 flushed_to_disk_lsn
- 2.8 flush链表中的lsn
- 2.9 checkpoint
一、redo日志的基本介绍
1.1 什么是redo日志
在真正访问页面之前,需要把在磁盘中的页加载到内存中的BufferPool中,之后才可以访问,现有如下场景,我们只在内存的BufferPool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么已提交的事务在数据库中所做的更改也就跟着丢失了,这是违背事务的持久性的。
就这个问题而言,大家可能想到的解决办法是,在事务提交之前,把事务修改过的所有页面都刷新到磁盘中,不过这个办法有下面几个缺陷:
- 有时我们只是修改了某个页面中的一个字节,此时如果将整个页面都刷新到磁盘中就有些太浪费了
- 随机I/O刷新起来很慢,一个事务可能会修改很多的页面,这些页面之间可能并不相邻,这就意味着在刷新时涉及很多的随机I/O。
redo日志给出的解决方法是,没必要每次在事务提交时就把事务在内存中的修改过的页面全部刷新到磁盘,只需把修改的内容记录下来就可以了,例如,某个事务将系统表空间的第99号页面中偏移量为1000处的字节从1改为0,然后在事务提交时,将该事务执行过程中产生的redo日志刷新到磁盘中就可以了。
使用redo日志的好处:
- redo日志占用的空间更小,在redo日志中只需记录表空间ID,页号,偏移量,更新的数据
- redo日志写入磁盘的过程是顺序I/O,因为某个事务产生的redo日志会按顺序放到相邻的页中
1.2 redo日志的格式
type:表示redo日志的类型
spaceID:表空间ID
page number:页号
data:redo日志的具体内容
1.3 redo日志的类型
- MLOG_REC_INSERT:表示在插入一条使用非紧凑行格式(REDUNDANT)的记录时,redo日志的类型
- MLOG_COMP_REC_INSERT type 字段对应的十进制数字为 38) 表示在插入一条使用紧凑行格式 COMPACT DYNAMIC OMPRESSED 的记录时 redo 日志的类型.
- MLOG_COMP]AGE_CREATE (type 字段对应的十进制数字为 58) 表示在创建一个存储紧凑行格式记录的页面时 red 日志的类型.
- MLOG_COMP_REC_DELETE type 字段对应的十进制数字为 42 ): 表示在删除一条使用紧凑行格式记录时 redo 日志的类型.
…
redo日志的类型有很多,用到哪个在详细说
1.4 Mini-Transaction
一条insert语句可能修改很多的页面,比如系统表空间页号为7的页面的Max Row ID 属性,还可能修改聚簇索引和二级索引对应的B+树中的页面,在执行语句的过程中产生的redo日志,InnoDB将这些日志划分成了很多不可分割的组。
例如:
- 向聚簇索引对应的B+树的页面插入一条记录产生的redo日志是一组的,是不可分割的
- 向某个二级索引对应的B+树的页面插入一条记录时产生的redo日志也是一组的,是不可分割的
【为什么是不可分割的呢?】
以向某个索引对应的 树中插入一条记录为例进行解释,首先定位这条记录该被插入到的数据页
乐观情况:该数据页剩余空间充足,直接将要插入的记录插入到该页面中即可,产生一条redo日志
悲观情况:该数据页剩余空间不足,需要进行页分裂操作,然后把原先数据页中的记录复制到新的数据页中…等一系列操作下来会产生很多条的redo日志,这些redo日志就是不可分割的组
redo日志是为了在系统因崩溃而重启时恢复崩溃前的状态而提出的,如果在悲观插入的过程只记录了一部分redo日志,那么在按照这一部分redo日志恢复出的B+树会是一种不正确的状态,因此在执行需要保证原子性的操作时,必须一组的形式记录redo日志,在进行恢复时,针对某个组中的redo日志,要么全部恢复,要么一条都不恢复。
【这在MySQL中是怎么做到的呢?】
某个需要保证原子性的操作所产生 一系 redo 日志,必须以一条类型为 MLOG
MULTl_REC_END redo 志结尾这样在系统因崩溃而重启恢复时,只有解析到类型为 MLOG MULTl REC END redo日志时,才认为解析到了一组完整的 redo 才会进行恢复:否则直接放弃前面解析到的redo日志
二、redo日志的写入过程
2.1 redo log block
为了更好的管理redo日志,InnoDB把通过MTR生成的redo日志都放到了大小为512字节的中
log block header中几个属性的含义:
HDR_NO:每个block都有一个编号
HDR_DATA_LEN:表示当前block已经使用了多少个字节
FIRST_REC_GROUP:表示该block中第一个MTR生成的redo日志记录组的偏移量
CHECKPOINT_NO:checkpoint的序号
2.2 redo日志缓冲区
InnoDB为了解决磁盘速度过慢的问题引入了BufferPool,同理,写入redo日志时也不会直接写到磁盘中,在服务器启动时向操作系统申请了一大片连续的内存空间称为redo log buffer,这片内存空间被分为若干个连续的redo log block
2.3 将redo日志写入log buffer
向log buffer中写入redo 日志是顺序写入的,通过一个 buf_free的全局变量,指明后续写入的redo日志应该写到log buffer的哪个位置
2.3 redo日志刷新到磁盘
redo日志刷盘时机,redo日志总不能一直待在内存中,会在某些时机将redo日志刷新到磁盘中,具体有:
- log buffer空间不足时,当redo日志占满log buffer的50%时,就需要将这些日志刷新到磁盘中
- 事务提交时
- 后台有一个线程,不断扫描log buffer,将redo日志刷新到磁盘
- 正常关闭服务器时
- 做checkpoint时
2.5 redo日志文件组
MySQL数据目录下默认有两个名为ib_logfile0和ig_logfile1的两个文件,log buffer默认刷新到这两个文件中。也就是说,redo日志文件不止一个,而是以日志文件组的形式出现: ib _logfile1 写满了就去 ib_logfile2; 依此类推.如果写到最后一个文件 , 时发现写满了,该咋办?那就重新转到 ib_logfile0继续写.
2.6 redo日志文件格式
redo日志文件也是由若干个512字节大小的block组成,
在redo日志文件组中,前2048(也就是前4个block)用来存储一些管理信息,后面的字节用来存储log buffer中的block镜像
2.7 log sequence number
log sequence number(lsn)是一个全局变量,记录当前总共已经写入的redo日志量,lsn的初始值是8704. 每一组由MTR生成的redo日志都有一个唯一的lsn值与其对应,lsn值越小,说明redo日志产生的越早。
2.8 flushed_to_disk_lsn
flushed_to_disk_lsn也是一个全局变量,表示刷新到磁盘中的redo日志量,初始值也是8704,随着系统的运行,不断有redo日志产生,不断被写入到log buffer,但并不会立即刷新到磁盘,此时lsn和flushed_to_disk_lsn就产生差距了
2.8 flush链表中的lsn
在MTR执行过程中修改过的页会被加入到Buffer Pool的flush链表中,第一次修改某个已经加载到Buffer Pool中的页面时,就会把这个页面对应的控制块插入到flush链表的头部,之后再修伊该页面时,由于它已经在 f1 1 链表中, 不再
了.
的控制块中记录两个关于页面何时修改属性:
- oldest_modification 第一次修改 Buffer Pool 中的某个缓冲页时, 就将修该页面MTR 开始时对应的lsn值写入这个属性
- newest_modification 每修改一次页面,都会将修改该页面的 MTR 束时对
lsn值写入这个属性.
2.9 checkpoint
由于redo日志文件组的容量是有限的,redo日志的作用是为了在系统崩溃后恢复脏页用的,如果对应的脏页已经刷新到磁盘中了,那么这些redo日志也就没有用了,因此判断redo日志占用的磁盘空间可不可以被覆盖的依据是,该redo日志对应的脏页有没有被刷新到磁盘中。
在InnoDB中,使用checkpoint_lsn表示当前系统中可以被覆盖的redo日志总量是多少,该变量的初始值是8704.
当页a被刷新到了磁盘上,那么此时操作页a时生成的redo日志就可以被覆盖了,然后进行一次增加check_point的操作,这个过程称为执行一次checkpoint
执行一次checkpoint分为两个步骤:
-
计算当前可以被覆盖的redo日志对应的lsn最大是多少
首先找到flush链表的尾节点,找到该尾节点的oldest_modification,凡是小于该oldest_modification的lsn对应的日志都可以被覆盖掉,因为这些redo日志对应的被修改过的页面都已经刷新到内存中了 -
将checkpoint_lsn 与对应的 redo 日志文件组偏移 以及此次checkpoint的编号写到日志文件管理信息〈就是 checkpoint1或者 checkpoint2 )中。
【用户线程批量从flush链表中刷出脏页】
内容参考《MySQL是怎样运行的》