1、redo log是什么
在访问页面时需要将磁盘中的页加载到Buffer Pool中,如果对Buffer Pool的缓冲页进行修改,在事务提交时,就必须把脏页刷新到磁盘上,不然一旦mysql故障宕机,提交事务所做更改就全部丢失了,不符合事务的持久性特性。
但是如果每次在事务提交时都把脏页刷新到磁盘存在一些问题:
1、刷新一个完整数据页太浪费了。如果修改了页面的某条数据的一个字节,需要将整个页面刷新到磁盘(页是InnoDB中磁盘和内存交互的基本单位)。
2、随机磁盘IO性能很低。修改的页面可能不是物理上相邻的,所以产生大量随机IO,特别是对于机械硬盘来说,随机IO很慢。
而redo log(重做日志)正好能够解决事务提交后事务对数据库的修改能永久生效且每次提交事务刷新脏页的问题。
WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘
redo log是物理日志,记录了页面的修改内容,具体记录的就是表空间号,数据页号,偏移量,修改了几个字节的值和具体的修改值。当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新缓冲页。
InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面。
redo log的优点:
- redo log占用空间很小
- redo log日志是顺序写盘的
redo log会把事务在执行过程中对数据库的所有修改都记录下来,在系统因崩溃而重启后可以把事务所做的任何修改都恢复过来。
2、redo log详解
2.1、redo log 页
在执行一些原子性操作时(如插入数据记录时需要插入数据到数据页,插入主键索引目录项,还可能页分裂,但是这些是一个原子操作),必须以组的形式记录redo log。InnoDB在一组的最后一条redo log后加上一条特殊类型的redo log,该类型的redo log名称为
MLOG_MULTI_REC_END,只有一个type字段。 这样在系统崩溃重启恢复时,只有解析到MLOG_MULTI_REC_END类型的redo log,才认为解析到了一组完整的redo log,才会进行恢复,否则直接放弃前面解析到的redo log。 InnoDB将对底层页面进行一次原子访问的过程称为一个Mini-Transaction(MTR)
InnoDB将通过MTR生成的redo log放到了大小为512字节的页中,将存储rodo log的页称为block。
2.2、redo log文件组
mysql 5.7.22版本中,redo log 文件默认为两个,每个redo log文件大小为 48MB。redo log文件为循环写,当最后一个redo log文件写满后,重新到第一个redo log 文件写,覆盖原redo log。
- innodb_log_group_home_dir :redo log文件所在目录
- innodb_log_file_size:每个redo log 文件大小
- innodb_log_files_in_group:redo log文件个数,默认值为2 ,最大值为100
show VARIABLES like '%innodb_log_group_home_dir'
show VARIABLES like '%innodb_log_file_size'
show VARIABLES like '%innodb_log_files_in_group'
log file header各属性具体含义
| 属性名 | 长度(字节) | 描述 |
|---|---|---|
| LOG_HEADER_FORMAT | 4 | redo日志的版本,在MySQL 57.22中永远为1 |
| LOG_HEADER_PAD1 | 4 | 用于字节填充,没什么实际意义 |
| LOG_HEADER_START_LSN | 8 | 标记本redo日志文件偏移量为2048字节处对应的lsn值 |
| LOG_HEADER_CREATOR | 32 | 一个字符串,标记本redo日志文件的创建者是谁。正常运行时该值为MySQL的版本号(比如“SQL 5.7.22”); 在使用mysqlbackup命令创建redo日志文件时,该值为“ibbackup”和创建时间 |
| LOG_BLOCK_CHECKSUM | 4 | 本block的校验值 |
checkpoint各属性具体含义
| 属性名 | 长度(字节) | 描述 |
|---|---|---|
| LOG_CHECKPOINT_NO | 8 | 服务器执行checkpoint 的编号,每执行一次checkpoint该值就加1 |
| LOG_CHECKPOINT_LSN | 8 | 服务器在结束checkpoint时对应的lsn值:系统在崩溃后恢复时将从该值开始 |
| LOG_CHECKPOINT_OFFSET | 8 | 上个属性中的lsn值在redo日志文件组中的偏移量 |
| LOG_CHECKPOINT_LOG_BUF_SIZE | 8 | 服务器在执行checkpoint操作时对应的logbufer的大小 |
| LOG_BLOCK_CHECKSUM | 4 | 本block的校验值 |
2.3、redo log缓冲区
为了解决磁盘过慢问题,redo log不是直接写入磁盘,而是 redo log buffer中,这片内存空间被划分为若干个连续的redo log block。可以通过参数innodb_log_buffer_size 来指定 redo log buffer大小,在mysql 5.7.22中默认为16MB。
show VARIABLES like '%innodb_log_buffer_size'
向log buffer中写入redo log的过程是顺序写入的,即先往前面的block中写,当该block的空闲空间用完之后再向下一个block写,InnoDB提供了一个名为buf_free的全局变量,该变量指明后续写入的redo日志应该到log buffer中的哪个位置。
2.4、redo日志刷盘时机
- redo log buffer空间不足时
redo log buffer 大小有限(通过参数innodb_log_buffer_size 指定),如果当前写入redo log buffer的日志量已经达到redo log buffer 总容量的50%,就需要将日志刷新到磁盘中。
- 事务提交时
为了保证事务的持久性,在事务提交时,必须把事务所对应的redo log刷新到磁盘,否则系统崩溃后,无法将该事务的修改恢复回来。
- 后台有一个线程,大约以每秒1次的刷新频率将redo log buffer中的redo log刷新到磁盘
- 正常关闭服务器时
- 做checkpoint时
3、log sequence number
3.1、lsn和flushed_to_disk_lsn
InnoDB使用lsn(log sequence number ) 的全局比变量来记录当前总共已经写入的redo log日志量,InnoDB规定lsn的初始值为8704。
InnoDB使用 **flushed_to_disk_lsn**的全局变量表示redo log buffer刷新到磁盘的日志量。当有新的redo log写入redo log buffer 时,首先lsn会增长,但是**flushed_to_disk_lsn**不变,随后随着不断有redo log buffer中的日志被刷新到磁盘,**flushed_to_disk_lsn**也会跟着增长,当两者值相同时,说明 redo log buffer中的日志都被刷新到磁盘了。
3.2、flush链表的lsn
点击查看【processon】
控制块中有两个关于页面何时修改的属性:
oldest_modification:第一次修改Buffer Pool中的某缓冲页时,就将修改该页面的MTR开始时对应的lsn值写入这个属性
newest_modification:每修改一次页面,都会将修改该页面的MTR结束时对应的lsn值写入这个属性,即该属性表示页面最近一次修改后对应的lsn值
flush链表中的脏页按照第一次修改发生的时间顺序进行排序,也就是按照oldest_modification代表的lsn值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值。
4、checkpoint
4.1、checkponit和checkpoint_lsn
redo log日志文件组的容量有限,我们不得不选择循环使用redo log 日志文件组的文件,但是会造成最后写入的redo log覆盖 最先写入的redo log。但是redo log只是为了在系统崩溃后恢复脏页使用的,如果对应的脏页已经刷新到磁盘,对应redo log就没有存在的必要了。所以判断redo log占用磁盘是否可以覆盖的依据就是对应脏页是否已经被刷新到磁盘了。
InnoDB 使用全局变量**checkpoint_lsn** 来表示当前系统可以被覆盖的lsn日志总量是多少,该变量的初始值也是8704。checkpoint操作即进行一次**checkpoint_lsn** 增加操作,表明有redo log日志刷盘,redo log 的lsn 比**checkpoint_lsn** 小的都可以覆盖。
特别注意:
刷新脏页和执行checkpoint是在不同线程上执行的,并不是说每次有脏页刷新都需要执行一次checkpoint
如下图,假设mtr_2写入redo log buffer时mtr_1对应的redo log 被刷新到了磁盘那么checkpoint_lsn 就为 8716 + 200 = 891
4.2、执行checkpoint操作步骤
(1)计算当前系统中可以被覆盖的redo log对应的lsn最大值是多少
刷新脏页的flush链表是以第一次修改发生的时间顺序进行排序,也就是按照oldest_modification代表的lsn值进行排序,那么该页面刷新到磁盘后,系统中lsn值小于该页面
oldest_modification值的redo log都可以被覆盖掉,将脏页的oldest_modification值赋给checkpoint_lsn
(2)将checkpoint_lsn与对应redo log日志文件组偏移量以及此次checkpoint编号写入日志文件的管理信息(chenckpoint1或者checkpoint2中)
InnoDB维护了一个checkpoint_no变量,用来统计目前系统执行了多少次checkpoint,每执行一次checkpoint,该变量值加一
计算得到该checkpoint_lsn在redo log日志文件组中对应的偏移量checkpoint_offset(checkpoint_lsn和checkpoint_offset是有转换关系的,比如checkpoint_lsn为8704时,checkpoint_offset为2048)
将checkpoint_no,checkpoint_lsn,checkpoint_offset三个值写到redo log 日志文件组第一个日志文件的管理信息中,当checkpoint_no为偶数时写到checkpoint1中,当checkpoint_no为奇数时写到checkpoint2中
checkpoint各属性具体含义
| 属性名 | 长度(字节) | 描述 |
|---|---|---|
| LOG_CHECKPOINT_NO | 8 | 服务器执行checkpoint 的编号,每执行一次checkpoint该值就加1 |
| LOG_CHECKPOINT_LSN | 8 | 服务器在结束checkpoint时对应的lsn值:系统在崩溃后恢复时将从该值开始 |
| LOG_CHECKPOINT_OFFSET | 8 | 上个属性中的lsn值在redo日志文件组中的偏移量 |
| LOG_CHECKPOINT_LOG_BUF_SIZE | 8 | 服务器在执行checkpoint操作时对应的logbufer的大小 |
| LOG_BLOCK_CHECKSUM | 4 | 本block的校验值 |
5、查看系统中的各种lsn值
SHOW ENGINE INNODB STATUS
将Status的数据复制粘贴到文本编辑器,查看其LOG部分:
---LOG---Log sequence number 376719138Log flushed up to 376719138Pages flushed up to 376719138Last checkpoint at 376719129
- Log sequence number表示系统中的lsn值,即当前系统已经写入的redo日志量,包括写入到log buffer 中的redo log
- Log flushed up to 表示flushed_to_disk_lsn的值,即当前系统一些如磁盘的redo log 日志量
- Pages flushed up to 表示 flush链表中被最早修改的那个页面对应的oldest_modification属性值
- Last checkpoint at 表示当前系统的checkpoint_lsn值
6、崩溃恢复
在数据库不发生崩溃的情况下,redo log只会带来额外的开销,但是一旦数据库发生崩溃,就可以根据redo log的记录将数据恢复到系统崩溃前的状态。
6.1、恢复的起点和重点
从redo log 日志组中第一个文件的管理信息中取出最近发生的那次check point 信息(只需比较checkpoint1 和checkpoint2中check_point_no值的大小),这样就能拿到最近发生checkpoint 对应的checkpoint_lsn以及它在redo log日志文件组 中的偏移量checkpoint_offset

普通block的log block header中有一个名为 LOG_BLOCK_HDR_DATA_LEN的属性,该属性值记录了当前block使用了多少字节的空间。对应普通被填满的block来说,该值为512,如果该值的属性不是512,那么该block就是崩溃扫描的最后一个block。
因崩溃而恢复系统时,只需要从checkpoint_lsn在日志文件组中对应的偏移量开始,一直扫描redo日志文件的block,直到某个block的LOG_BLOCK_HDR_DATA_LEN值不等于512。
6.3、怎么恢复
(1)使用哈希表
根据redo日志的space ID和page number属性计算出哈希值,把space ID和page number相同的redo日志放到哈希表的同一个槽里,如果有多个space ID和page number都相同的redo日志,那么它们之间按照生成的先后顺序链接起来使用链表连接起来。之后就可以遍历哈希表,因为对同一个页面进行修改的redo日志都放在了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机IO),这样可以加快恢复速度
(2)跳过已经刷新到磁盘的页
对于checkpoint_lsn的redo日志,它所对应的脏页肯定已经刷新到磁盘中了,但是对应lsn值大于checkpoint_lsn的redo log,它所对应的脏页不能确定是否已经刷到磁盘中了。
那么在恢复时怎么知道某个redo日志对应的脏页是否在崩溃发生时已经刷新到磁盘了呢?
每个页面的File Header部分有一个FIL_PAGE_LSN的属性,该属性记录了最近一次修改页面时对应lsn的值(其实就是页面控制块的newest_modification属性值),如果在执行了某次checkpoint后有脏页被刷新到磁盘,那么页对应的FIL_PAGE_LSN值一定大于checkpoint_lsn的值。凡是页对应的FIL_PAGE_LSN值大于checkpoint_lsn的值就不需要根据redo log进行恢复了。
