MySQL阅读笔记——14.redo日志
14.1 问题的提出
对数据库的操作都是以 页 为单位加载到 buffer pool
操作的,防止故障引起数据丢失,保证 持久性 如果每次提交事务都将页面刷到磁盘,会有两个问题。一是事务操作的页面可能并不相邻,随机I/O。二是InnoDB以页为单位进行磁盘I/O,即使只修改一个字段。只需要把修改的数据记录一下,保证系统故障后可以恢复即可 ,因此提出了redo日志的概念
redo日志占用空间很小,存储表空间id、页号、偏移量和需要更新的值,并且redo日志是顺序I/O,组提交降低了IOPS
Undo日志是InnoDB引擎特有日志,而MyISAM没有故障恢复能力,依赖Server层的binlog日志(所有引擎都可以使用),redo日志是循环写,用于故障恢复,一定大小后会覆盖之前日志;binlog是顺序写,用于数据库的增量恢复(归档) 事务操作写入redo日志后会进入 prepare 阶段,之后会写入binlog日志,提交事务后会进入 commit,这就是 两阶段提交, 两阶段提交
binlog有两种模式:
statement
模式记录sql,row
格式一次改动记录两条内容,一条改动前一条改动后。一般采用row
14.2 redo日志结构
属性 | 说明 |
---|---|
type | 日志类型 |
space ID | 表空间ID |
page number | 页号 |
data | redo具体内容 |
redo日志根据不同type划分为不同的类型,其中有一些只是指明在某个偏移量修改几个字节的值,以及具体被修改的内容,叫做 物理日志 ,还有一些redo日志除了指明对哪个表空间哪个页哪个位置进行修改的物理意义,还需要在处理的时候调用一些函数,等逻辑层面处理,如:insert操作可能要插入数据,修改页头,更新槽信息等一系列操作
14.3 redo日志写入
LOG_BLOCK_HDR_NO = ((lsn / 512) & 0x3FFFFFFFUL) + 1
其中0x3FFFFFFFUL代表1GB,也就是说系统最多能产生不重复的LOG_BLOCK_HDR_NO
值只有1GB
个 另外,LOG_BLOCK_HDR_NO
值的第一个比特位比较特殊,称之为flush bit
,如果该值为1,代表着本block是在某次将log buffer
中的block刷新到磁盘的操作中的第一个被刷入的block。
14.4 redo刷盘
mtr生成的redo日志在一定条件下会刷新到磁盘保存,默认保存在MySQL数据目录下的ib_logfile0
和ib_logfile1
的文件中(通过show variables like 'datadir'
命令查看),一个写满了就向另一个文件写入,如果全都写满了则重新转到ib_logfile0
进行覆写
事务未提交情况下也会刷盘:
MySQL后台线程每隔一秒就会把
redo log buffer
中的日志写入到页缓存
redo log buffer
占用innodb_log_buffer_size
的一半并行事务提交,顺带刷盘
通过启动参数可以调节ib_logfile innodb_log_group_home_dir:指定redo日志文件所在目录 innodb_log_file_size:指定每个redo日志文件大小(MySQL5.7默认48MB) innodb_log_files_in_group:redo日志文件个数(默认为2,最大100)
redo日志文件最大值=
innodb_log_file_size × innodb_log_files_in_group
14.4.1 redo日志文件格式
redo日志文件由若干的512字节的block组成(block就是存储redo的 页),redo日志文件组中每个文件大小、格式都一样(前2048字节,即前4个block存储一些管理信息,后面的字节存储log buffer
中的镜像)
属性名 | 长度(单位:字节) | 描述 |
---|---|---|
LOG_HEADER_FORMAT |
4 |
redo 日志的版本,在MySQL 5.7.21 中该值永远为1 |
LOG_HEADER_PAD1 |
4 |
做字节填充用的,没什么实际意义,忽略~ |
LOG_HEADER_START_LSN |
8 |
标记本redo 日志文件开始的LSN值,也就是文件偏移量为2048字节初对应的LSN值(关于什么是LSN我们稍后再看哈,看不懂的先忽略)。 |
LOG_HEADER_CREATOR |
32 |
一个字符串,标记本redo 日志文件的创建者是谁。正常运行时该值为MySQL 的版本号,比如:"MySQL 5.7.21" ,使用mysqlbackup 命令创建的redo 日志文件的该值为"ibbackup" 和创建时间。 |
LOG_BLOCK_CHECKSUM |
4 |
本block的校验值,所有block都有,我们不关心 |
14.4.2 Log Sequence Number(LSN)
redo日志不断递增,不会缩减,因此MySQL通过LSN记录redo日志写入到log buffer
量(初始值为8704),每一组mtr生成redo日志都有唯一的LSN与之对应,LSN越小说明redo日志产生越早,redo日志先被写入到log buffer
中,之后才逐步刷新到磁盘的ib_logfile[n]
文件中,因此使用了flushed_to_disk_lsn
全局变量标记log buffer
中哪些日志被刷新到磁盘,当有新的redo日志写入到log buffer
时,lsn会增长,但flushed_to_disk_lsn
不变,随着log buffer
不断刷到磁盘,flushed_to_disk_lsn
也随之增长,直到两者相同,此时log buffer
中所有的redo日志都刷新到磁盘ib_logfile[n]
文件中
向磁盘写入文件是先写入到操作系统缓冲区中,如果需要将缓冲区数据写到磁盘,需要调用操作系统提供的 fsync函数,只有当系统执行 fsync函数 将数据从缓冲区刷新到磁盘
flushed_to_disk_lsn
才会随之增长,如果只是刷新到缓冲区则write_lsn
增长而flushed_to_disk_lsn
不会增长
mtr结束,会把对应的一组redo日志写入到log buffer
,之后将mtr过程中可能修改的页面(缓存页)加入到Buffer Pool
的flush链表,并且修改并且修改对应控制块的oldest_modification
和newest_modification
(如果是第一次缓存该页面,则会把它的控制块加入到flush链表头部
,后续修改不会重新插入。即:flush链表中脏页是按照页面第一次修改时间从大到小排序)
oldest_modification:某页面加载到
Bufffer Pool
后第一次修改,修改该mtr开始时对应的lsn值newest_modification:每修改一次该页面,mrt结束时对应的lsn值,即,该页面最后一次修改的lsn值
14.4.3 checkpoint
redo日志文件组(ib_logfile[n]
)容量有限,当其中的redo日志对应的脏页已经刷新到磁盘则它占用的磁盘空间可以覆盖(注意是脏页数据刷盘,而只是redo日志刷盘),使用checkpoint_lsn
全局变量代表log buffer
可以被覆盖的redo日志量(初是8704)
增加checkpoint_lsn的操作称为一次 checkpoint,具体的 checkpoint_lsn 步骤如下:
获取
buffer pool
中最早被修改的的脏页对应的oldest_modification
,将该值赋给checkpoint_lsn
(小于该值均可被覆盖)将
checkpoint_lsn
和redo日志文件组偏移量以及checkpoint_no
写入到ib_logfile[n]
文件的前四个block中的checkpoint1
或者checkpoint2
(只会写到日志文件组的第一个日志文件中,checkpoint_no
为偶数写入到checkpoint1
,为奇数写入到checkpoint2
)
show engine innodb status \G
Log sequence number 124476971 #系统lsn值,也就是写入log buffer的redo量
Log flushed up to 124099769 #系统写入磁盘的日志量
Pages flushed up to 124052503 #flush链表中最早被修改页面的对应的oldest_modification
Last checkpoint at 124052494 #当前checkpoint_lsn
0 pending log flushes, 0 pending chkp writes
24 log i/o's done, 2.00 log i/o's/second
通过
innodb_flush_log_at_trx_commit
可以修改事务持久性0:提交事务不立即向磁盘同步redo日志,而是交给后台线程做
1:提交事务立即将redo日志刷盘,保证事务持久性,同时也是 默认值
2:提交事务redo日志写到操作系统缓冲区,但并不保证刷新到磁盘
14.5 MySQL奔溃恢复
ib_logfile[n]
文件组第一个文件中的前4个block页存储管理信息,其中两个block页分别存储checkpoint1
和checkpoint2
(checkpoint_no
为偶数写入到checkpoint1
,为奇数写入到checkpoint2
),从这两个位置获取最新的checkpoint_lsn
信息(值大的)即得到了 恢复的起点,每个block页面的log block header
部分的LOG_BLOCK_HDR_DATA_LEN
属性记录了当前block页面中使用了多少空间(初始为12,全部占用为512),如果该block页面没有被占满(即LOG_BLOCK_HDR_DATA_LEN
值不是512)则该值就是 恢复的终点 即此次恢复扫描的最后一个block页面
checkpoint_lsn
之前的记录都被刷新到磁盘,不需要恢复,checkpoint_lsn
之后的记录可能被刷到磁盘,也可能没有。需要确定checkpoint_lsn
之后到flushed_to_disk_lsn
要恢复的数据,主要是因为在最近做的一次checkpoint
后,可能后台线程又不断的从LRU链表
和flush链表
中将一些脏页刷出Buffer Pool
。这些在checkpoint_lsn
之后的redo
日志,如果它们对应的脏页在崩溃发生时已经刷新到磁盘,那在恢复时也就没有必要根据redo
日志的内容修改该页面了。 具体做法是对数据页恢复过程中查找File Header
部分的FIL_PAGE_LSN
属性,该属性记载了最近一次修改页面时对应的lsn
值(其实就是页面控制块中的newest_modification
值)如果在做了某次checkpoint
之后有脏页被刷新到磁盘中,那么该页对应的FIL_PAGE_LSN
代表的lsn
值肯定大于checkpoint_lsn
的值,凡是符合这种情况的页面就不需要重复执行lsn值小于FIL_PAGE_LSN
的redo日志了,所以更进一步提升了崩溃恢复的速度。
一个事务提交前需要两次刷盘,一次redo log,一次binlog