前面说了redo日志的格式,刷新到磁盘后台有线程每秒运行一次,还有事务提交的时候,buffer pool不会刷新到磁盘,但是log buffer会刷新到磁盘。Log buffer会分成若干的block,吧这些block持久化到磁盘上,mysql根目录有两个log_file0和log_file1,可以增加,当存满的时候,从0循环继续存储,默认是48Mb。每个block是512个字节,前面四个block占比2048个字节是记录管理信息,2048字节后面开始记录block。
Log sequence number
自系统修改开始,就不断的修改页面,也就不断的生成redo日志。为了记录一共生成了多少日志,于是mysql设计了全局变量log sequence number,简称lsn,但不是从0开始,是从8704开始。
我们知道log buffer中写入redo日志不是一条一条写入的,而是mtr生成的一组redo日志为单位写入,实际吧内容写在log block body处,但在统计lsn增长量时,是按照实际写入日志量占用log block header和log block tralider来计算的。举个例子:
例1:系统第一次启动初始化log buffer时,buf_free会指向第一个block偏移量为12个字节大小的地方,那么lsn值也会增加12。
那么lsn值就会记录成:8704+12 = 8716
例2:如果以mtr为一组redo日志占用存储的空间比较小,则会吧mtr占用的空间加进去。
那么lsn值就会记录成8716+200 = 8916
例3:如果以mtr为一组redo日志占用的存储空间比较大,则会吧log block header 和log block trailder占用的12个字节和4个字节也算进去。假设mtr2占用了三个block,此刻里面包含两个header 和两个trailder。
那么lsn值就会记录成1000+2*12 +4 * 2 = 1032 + 8716
至于为什么开始设置为8704呢,这是mysql规定好的,从上可以指定,每一组mtr都对应一个lsn值,如果lsn值越小,则redo日志生产的越早。
Flushed_to_disk_lsn
Redo日志首先写到log buffer中,之后才会被刷新到磁盘的redo日志文件上(ib_file0和ib_file1)。所以innoDB设计了一个全局变量buf_next_to_write,这个字段前面的数据都是已经被刷新到磁盘上的。所以log buffer的组成应该是:第一部分是到buf_next_to_write表示已经持久化到磁盘的数据,buf_next_to_write之后到buf_free表示记录的数据,还未持久化,buf_free之后的数据则是空间的log buffer。
我们前面说过lsn值代表redo日志量,包括未刷新到磁盘的redo日志,相对应的,innoDB设计了从 redo日志刷新到磁盘的全局变量,称为flushed_to_disk_lsn。系统第一次启动,lsn和flushed_to_disk_lsn值是相同的,但随着redo不断持久化到磁盘,就拉开距离了。
举例场景:当有mtr1:8716~8916
Mtr2:8916~9948
Mtr3:9948~10000
这时候因为还未持久化,所以flushed_to_disk_lsn还是8716,但是lsn值已经到了10000。
当mtr1和mtr2持久化到磁盘的时候,lsn值不变,flushed_to_disk_lsn则变为9948.
由上可以知道,当有新的redo日志,lsn值会增长,fulshed_to_disk_lsn不变,当持久化数据到磁盘的时候,则lsn不变,flushed_to_disk_lsn增长。当他们值相同的时候,说明所有redo日志已经持久化到磁盘。
LSN值和redo日志文件偏移量对应关系
因为lsn值是日志增长量的和,所以偏移量可以直接用lsn值减去初始的lsn值8704,剩下的就是redo日志文件偏移量。
Flush链表中的LSN
我们知道mtr代表一次对底层页面原子的访问,每次mtr结束的时候,都会吧redo日志写入到log buffer 中。除此之外,在mtr结束还有重要的事要做,写入到buffer pool的flush 链表中。回忆一下
Buffer pool是由控制块+碎片+缓存页组成。
而flush链表存的是还未持久化完成的脏数据,吧这些控制块通过双向链接组成链表,头部有一个链表基节点,有总的控制块数量,及其strat位子和end位子。
当第一次修改了buffer pool中的页面,则会吧这个控制块放入这个链表里,也就是链表里的位子顺序是根据第一次修改来放入的,后面就算修改因为已经存在了,直接在flush 链表修改就好。
Oldest_modification:如果某个页面被加载到buffer pool后进行第一次修改,那么就将mtr对应的lsn值写入这个。
Newest_modification:每次修改页面都会在mtr结束时候,吧lsn值写入这个。
而且每次新增的的修改页会放在flush链表的最前面。
综上知道,当第一次修改会在flush链表新增控制块,并且放在最前面,olderst_modification写入lsn值,newest_modification则是写入mtr结束时候的lsn值。如果当前页已经存在flush链表,则不需要新增当前页,old值不需要修改,吧newest值修改成mtr结束的lsn值即可。
Checkpoint
前面我们说过,log buffer内存是优先的,当使用完毕之后,我们得从第一个文件重新循环使用,所以这样很容易追尾。那我们可以想想redo日志干嘛的,是为了防止系统崩溃,记录数据,吧数据持久化到磁盘上的,那么如果已经把这些数据持久化到磁盘了,是不是系统崩溃这些数据也不需要恢复,可以放弃了呢?
用我们上面的例子,mtr1和mtr2和mtr2,当1和2已经持久化到磁盘,但修改的脏数据仍然留在buffer pool里,所以他们生成在redo日志在磁盘里的空间是不可以覆盖的。随着系统的运行,当页已经被刷新到磁盘里,这时候控制块就会从flush链表中移除。
所以这时候innoDB设计了checkpoint_lsn全局变量来代表当前系统可以覆盖的redo日志总量,这个值默认也是8704。
比方说页a刷新到磁盘上,mtr1生成的redo日志就可以被覆盖了,我们可以进行增加一个checkpoint_lsn的操作,这个过程我们称做checkpoint。做一次checkpoint可以分为两个步骤:
步骤一:计算一下当前系统中可以覆盖的redo日志对应的lsn值是多少
Redo日志可以覆盖,意味着脏数据已经刷新到磁盘,我们只要知道最早修改的脏页对应的oldest_modification,凡是checkpoint_lsn小于这个值的,都是可以覆盖的,比如我们前面的例子,最小的oldest_ modification值是8916,这时候我们吧8916赋值给checkpoint_lsn,小于这个值的都可以覆盖。
步骤二:将checkpoint _lsn和对应的redo日志文件组偏移量以及checkpoint编号写到日志文件管理信息(就是checkpoint1和checkpoint2)中
innoDB设计了一个全局变量,checkpoint_no,每做一次point就+1,还有计算checkpoint_offset偏移量,可以通过checkpoint_lsn计算。
每个redo都有2048个管理文件的信息,当checkpoint_no是偶数的时候,就存储在checpoint1中,当checkpoint_no是奇数的时候,就存储在checkpoint2中。
批量从flush链表刷出脏页
一般情况下,后台的线程都会对flush链表和lru链表刷脏到磁盘上,主要磁盘I/O操作比较慢,不想影响用户线程的处理请求。但如果系统操作频繁,写日志频繁,系统lsn增值太快。如果后台无法及时持久化脏数据,无法checkpoint,这时候可能需要用户线程同步刷新脏数据到磁盘,这样脏数据对应的redo日志没用了,就可以去checkpoint。
查看系统中的各种LSN值
我们可以用show engine innodb status;查看lsn值
其中log_sequence_number:代表系统中的lsn值,也就是当前系统的redo日志量,包括写入log buffer中的日志
Log_flushed_up_to:代表flushed_to_disk_lsn的值,代表已经写入的磁盘的redo日志字节量。
Page flushed up to:代表fulsh链表中最早修改那个页面对应的oldest_modification属性。
Last checkpoint at:当前系统的checkpoint_lsn值。