1.如何提高重做日志的写入性能?为了保证重做日志不丢失,会在磁盘中开辟一个空间来保存日志。但是这样会有一个问题,磁盘的读写性能很差。因此,重做日志与数据页是一样的。系统会分配一块连续的内存来提高读写性能;数据页对应缓冲池,重做日志对应日志缓冲区。缓冲池可以使用“innodb_buffer_pool_size”指定总大小,“innodb_buffer_pool_instances”指定实例数,但大小必须大于等于1G才能生效。日志缓冲区可以使用“innodb_log_buffer_size”来指定日志缓冲区的大小;一个连续的内存空间会被分成N个512字节大小的块。日志文件可以使用“innodb_log_file_size”来指定每个日志文件的大小,使用“innodb_log_files_in_group”来指定日志文件的总数。2.重做日志什么时候写入日志缓冲区?对底层页面(可能是多个页面)的一次原子访问等于一次MTR,即MiniTransaction。一个MTR对应一组重做日志。一个transaction对应多个statement,一个statement对应多个MTR,一个MTR对应一组redologs,即多个redologs。MTR结束后,会向logbuffer中写入一组redologs。详见下图:3.logbuffer中的redologs应该在什么时候flush?当logbuffer写了一半左右的时候,中间下次写redolog的时候需要把logbuffer中的redolog刷到磁盘文件中。当事务结束时,需要将logbuffer中修改的cachepage对应的redolog刷回磁盘。后台线程大约每秒将日志缓冲区中的重做日志刷新到磁盘。执行检查点。优雅地关闭服务器。4、我们都知道redolog每次写入都是以group为单位的,那么怎么知道哪个是group呢?在组中最后一个redolog之后添加一个特殊类型的redolog,类型名称为“MLOG_MULTI_REC_END”,type字段对应的十进制数为31。这种类型的redolog结构非常简单,只有一种类型的字段。5.我如何知道下一个重做日志将被重写在日志缓冲区的哪个位置?buf_free全局变量指向日志缓冲区中的下一个写入位置。6、如何知道下次从日志缓冲区的哪个位置写入磁盘?buf_next_to_write全局变量指向日志缓冲区中要刷新回磁盘的下一个位置。7、如何定位logbuffer中redologs对应的修改数据页;在修改过的数据页中,如何定位对应的redologs?当修改的缓存页在MTR结束时找到对应的重做日志,修改后的数据页对应的数据块会被放入flush链表的头部,赋值两个参数,分别是old_modification和new_modification:old_m赋值是MTR开始前的lsn值,new_m赋值为MTR结束时的lsn值。如果一个MTR修改的数据页对应的控制块已经在flush列表中,则不调整该数据页对应的数据块的位置,而是修改new_modification的值,old_modification仍然保持值第一次进入刷新列表时的lsn。也就是说,在flush链表中,数据块按照第一次修改的时间倒序排列。首先有个变量叫lsn,全称:logsequencenumber,日志序号。它记录了重做日志的总字节数,初始值为8704。系统启动初始化logbuffer时,lsn值为8704+12(一个logblockheader)=8716那么logbuffer是由多个block组成的(可以理解为bufferpullcachepages),block由三部分组成,日志块头(12字节),日志块体,日志块尾(4字节)。当第一个重做日志组,如“mt_1”,准备写入,且一个block可以容纳它时,lsn为8704+12(一个日志块头)=8716,假设“mt_1”一共有100个字节,那么写入“Aftermt_1”,则lsn为8716+100=8816。当第二个重做日志组,如“mt_2”,准备写入,需要跨块容纳,如跨块(即包含日志块头和日志块尾),lsn写入前:8816,假设“mt_2”一共1000字节,那么写入“mt_2”后,lsn为8816+12(一个logheader)+4(一个logtail)+1000=9832lsnflushandlsnpass以上,那么我们就可以根据flushlist中数据块的old_modification和new_modification找到对应的redolog集合,因为可以通过lsn定位到对应的redolog在磁盘文件中的offset(下面会解释)。在redolog中找到对应的cachepageredolog的一般结构是:type-spaceIdID-pageNumber-data,即我们可以根据redolog的spaceID和pageNumber找到对应的cachepage.顺便说一句:在InnoDB中,有一个哈希表,key是表空间号+页号,value是缓存页的地址。这样我们就可以通过空间ID和页码快速定位到对应的缓存页。8、我们知道可以通过lsn知道有多少字节的重做日志写入了日志缓冲区,那么是否可以有对应的变量来知道有多少字节的重做日志被刷到磁盘?flushed_to_disk_lsn全局变量,指示刷新到磁盘的日志量。9.lsn和日志文件的偏移量如何匹配?lsn的初始值为8704,随着redolog的不断写入,lsn不断增加。在InnoDB中,block的结构用于存储重做日志(无论是logbuffer还是logfile),block包含三部分,如前所述。当redolog不断写入,占用了block的空间,那么lsn就会增加相应的字节数。当然,除了body,还算header和trailer。日志文件由日志组组成。日志组中的最大文件数为100。每个日志文件也由多个512字节的块图像组成。日志组中第一个日志文件的前4个块映像用于存储重要信息,如checkpoint等,即前2048字节不用于存储redologs,即redologs的存储量是从2048字节开始计算的。在日志文件的日志文件头中有一个“LOG_HEADER_START_LSN”属性,它在这个重做日志文件的offset2048字节处标记了对应的lsn值。详见下图:10.logbuffer中的redolog真的会在事务结束时立即刷新回磁盘吗?默认是yes,还有一个参数控制:"innodb_flushing_log_at_trx_commit",默认值为10:事务提交,不会立即刷入磁盘,依赖后台线程刷入,即如果MySQL或此时系统挂起重启,脏页无法恢复1:事务提交,logbuffer的redolog会立即刷回磁盘2:事务提交会立即flushlogbuffer的redolog到操作系统的缓存而不是刷新到磁盘;如果这时候MySQL挂掉了,不会影响重启后脏页的恢复,而且如果系统挂掉了,是没有办法恢复的。11、日志文件是循环使用的,也就是可以覆盖的,那么如何判断是否可以覆盖呢?如果日志文件可以被覆盖,首要条件是重做日志对应的脏页已经刷到磁盘。Innodb有一个全局变量:checkpoint_lsn,它记录了可以覆盖的redolog数量。初始值为lsn的初始值,8704,当一个脏页刷入磁盘时,首先获取flus链表中最早的缓存页,即需要获取链表尾部的控制块,然后得到old_modification的值,然后把这个值赋值给checkpoint_lsn,因为只要lsn小于flushlist中最旧控制块的old_modification就意味着可以覆盖,毕竟对应的dirty页已刷新到磁盘。然后会根据当前的checkpoint_lsn得到对应日志文件组的offset,记录为checkpoint_offset,checkpoint_no也需要加1,最后将这三个信息记录在日志文件组的checkpoint1或者checkpoint2中(checkpoint_no为奇数,存1,否则存2)。以上两个步骤称为执行检查点。什么是检查点?我们只需要从日志文件组中的checkpoint1和checkpoint2获取信息,然后比较checkpoint_no看哪个是最新的,然后获取checkpoint_lsn,那么lsn小于checkpoint_lsn的日志就可以覆盖了。12、当系统死机重启时,如何使用redologs进行恢复?crashrecovery的redologs主要用到上面提到的checkpoint_lsn,因为checkpoint_lsn表示的是可以覆盖的日志量,意味着checkpoint_lsn之前的redologs对应的脏页已经被删除闪回磁盘。首先从redolog组中得到checkpoint1和checkpoint2,然后判断谁的checkpoint_no更大,更大的就是最近执行的checkpoint。然后获取对应的checkpoint_offset,那么checkpoint_offset之后的redologs需要扫描一次,然后根据redolog的内容恢复datapage。13.恢复是否扫描重做日志并执行恢复?问题:因为数据页的变化是根据重做日志恢复的,所以直接更新了磁盘中的数据页;扫描重做日志,并执行恢复。如果有多个redolog记录同一个数据页的变化,而且不连续,会导致多次随机IO,性能会很差。解决方案:于是就会有一个哈希表,key是空间ID+pageNumber,value是数据页的地址。扫描redologs时,所有具有相同spaceID+pageNumber的redologs会被放在同一个slot下。然后遍历hash表,执行所有redolog对应的每个spaceID+pageNumber。好处:避免多次随机IO,提高恢复速度。根据redolog恢复,避免恢复的顺序问题。详见下图:14.恢复时,怎么知道什么时候结束?首先我们知道loggroup中有多个blockimage,然后flushredolog,依次填充每个block。当前一个块已满时,填充下一个。那么,每个块的大小为512字节,包括日志块头、日志块体和日志块尾。在块页结构中,日志块头有一个“LOG_BLOCK_HDR_DATA_LEN”属性,记录了当前块使用了多少字节的空间。对于一个已满的block,这个值一直是512。最后再往回扫描,直到logblockheader中的“LOG_BLOCK_HDR_DATA_LEN”属性不是512的block,则恢复结束。15、如何兼容脏页已经刷回磁盘,而重做日志还没有刷回磁盘的场景?场景复现:当我们提交一个事务时,会根据参数“innodb_flush_at_trx_commit”进行下一步操作。如果是0或者2,那么此时的日志并没有刷回磁盘,而是保留在日志缓冲区或者操作系统缓存中。那么,如果有后台线程将LRU链表或者flush链表的一些脏页刷回磁盘,flush之后;但是此时对应的redolog还停留在上面提到的两个地方,如果服务器宕机,那么对应的redolog就会丢失。因为刷新LRU链表、刷新链表和刷新redolog的后台线程往往是不同的线程,所以无法得知相应的redolog是否被刷新回来了。Compatibility:每个数据页都有一个叫做FileHeader的部分,在FileHeader中有一个属性叫做FIL_PAGE_LSN,它记录了该页最后一次修改时对应的lsn值(其实就是页控制块newest_modification的值)。如果一个脏页在某个checkpoint后刷入磁盘,那么该页对应的FIL_PAGE_LSN所代表的lsn值必须大于checkpoint_lsn的值,任何满足这种情况的page都不需要重复执行lsn值小于FIL_PAGE_LSN重做日志,
