具体细节 请去掘金购买《MySQL 是怎样运行的:从根儿上理解 MySQL》
redo日志刷盘时机
- 1.当MTR产生的一组redo日志在mtr结束的时候会被复制到log buffer里面
- 2.mtr是在对应的语句执行完毕时候结束
- 3.log buffer刷入磁盘的时机分为以下几种:
4.log buffer 空间不足
- 1.通过系统变量innodb_log_buffer_size指定 logbuffer的大小
- 2.当redo日质量占满了log buffer一半就需要把日志刷新到磁盘上(可能会存在事务未提交,但是也被刷新到磁盘)
5.事务提交
- 1.在事务提交的时候需要同步把log buffer刷新到磁盘,才能保证事务的持久性
6.后台线程不停的刷
- 1.后台还有线程,每秒刷新一次log buffer中的日志到磁盘
7.正常关闭服务器的时候
8.check point时候
redo日志文件组
- 1.在mysql的数据目录下默认有ib_logfile0和ib_logfile1的文件,这就是对应的redo日志文件
- 2.通过系统变量innodb_log_group_home_dir指定了redo日志文件所在的目录
- 3.通过系统变量innodb_log_file_size指定了每个redo日志文件的大小,在MySQL 5.7.21这个版本中的默认值为48MB
- 4.通过系统变量innodb_log_files_in_group指定redo日志文件的个数,默认值为2,最大值为100。
- 5.总共的redo日志文件大小其实就是:innodb_log_file_size × innodb_log_files_in_group。
- 6.redo日志文件组,都是循环写入,一个文件写满了就写下一个,写到结尾继续循环(从redo日志文件的第2048个字节开始)写入(当然会把一些事物修改的结果落入数据页才可以覆盖)
redo日志文件格式
- 1.redo日志文件和redo日志分配的block大小相同
- 2.redo日志文件组的每个文件大小都一样,格式也一样都是由下面两部分组成
- 3.前2048个字节(也就是4个block)用来存在一些管理信息的
- 4.从第2048个字节往后是用来存储logbuffer中的block
- 5.所以每次循环使用是从2048个字节开始
redo日志文件的前四个block
- 1.log file header:描述该redo日志文件的一些整体属性
- 2.checkpoint1:记录关于checkpoint的一些属性
- 3.没用
- 4.checkpoint2
log file header
- 1.LOG_HEADER_FORMAT: redo日志的版本,在MySQL 5.7.21中该值永远为1
- 2.LOG_HEADER_START_LSN:标记本redo日志文件开始的LSN值,也就是文件偏移量为2048字节初对应的LSN值
- 3.LOG_HEADER_CREATOR:一个字符串,标记本redo日志文件的创建者是谁。正常运行时该值为MySQL的版本号
- 4.LOG_BLOCK_CHECKSUM:本block的校验值
- 5.LOG_HEADER_PAD1:做字节填充用的
checkpoint1和checkpoint2的属性结构
- 1.LOG_CHECKPOINT_NO:服务器做checkpoint的编号,每做一次checkpoint,该值就加1
- 2.LOG_CHECKPOINT_LSN:服务器做checkpoint结束时对应的LSN值,系统奔溃恢复时将从该值开始
- 3.LOG_CHECKPOINT_OFFSET:上个属性中的LSN值在redo日志文件组中的偏移量
- 4.LOG_CHECKPOINT_LOG_BUF_SIZE: 服务器在做checkpoint操作时对应的log buffer的大小
- 5.LOG_BLOCK_CHECKSUM:本block的校验值
Log Sequeue Number(日志序列号)
- 1.InnoDB为记录已经写入的redo日志量,设计了一个称之为Log Sequeue Number的全局变量
- 2.LSN的初始值为8704
- 3.统计log buffer写入到redo日志的量,不仅仅包含日志本身,还包含log block header和log block trailer
- 4.每一组由mtr生成的redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早
flushed_to_disk_lsn
- 1.全局变量buf_next_to_write标志着log buffer 中已经有哪些日志被刷新到磁盘中
- 2.flushed_to_disk_lsn代表刷新到磁盘中的redo日志量的全局变量--该变量的值和初始的lsn值是相同的,都是8704
- 3.如果LSN和flushed_to_disk_lsn如果两者的值相同时,说明log buffer中的所有redo日志都已经刷新到磁盘中了。
- 4.其实只有当系统执行了fsync函数后,flushed_to_disk_lsn的值才会跟着增长,当仅仅把log buffer中的日志写入到操作系统缓冲区却没有显式的刷新到磁盘时,另外的一个称之为write_lsn的值跟着增长
lsn值和redo日志文件偏移量的对应关系
flush链表中的LSN
- 1.在mtr结束时,把在mtr执行过程中可能修改过的页面加入到Buffer Pool的flush链表
- 2.flush链表中的脏页是按照页面的第一次修改时间从大到小进行排序的
- 3.缓存页对应的控制块中记录两个关于页面何时修改的属性:oldest_modification和newest_modification
- 4.oldest_modification:如果某个页面被加载到Buffer Pool后进行第一次修改,那么就将修改该页面的mtr开始时对应的lsn值写入这个属性
- 5.newest_modification:每修改一次页面,都会将修改该页面的mtr结束时对应的lsn值写入这个属性。也就是说该属性表示页面最近一次修改后对应的系统lsn值
- 6.flush链表中的脏页按照修改发生的时间顺序进行排序,也就是按照oldest_modification代表的LSN值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值
checkpoint
- 1.判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里
- 2.checkpoint_lsn:代表当前系统中可以被覆盖的redo日志总量是多少,这个变量初始值也是8704。
- 3.增加checkpoint_lsn就是叫checkpoint
步骤一:计算一下当前系统中可以被覆盖的redo日志对应的lsn值最大是多少。
- 1.寻找最早修改的脏页对应的oldest_modification--只要查看flush链表最后一个页面的oldest_modification
- 2.那凡是在系统lsn值小于该节点的oldest_modification值时产生的redo日志都是可以被覆盖掉的
- 3.我们就把该脏页的oldest_modification赋值给checkpoint_lsn。
步骤二:将checkpoint_lsn和对应的redo日志文件组偏移量以及此次checkpint的编号写到日志文件的管理信息(就是checkpoint1或者checkpoint2)中。
- 1.InnoDB维护了一个目前系统做了多少次checkpoint的变量checkpoint_no
- 2.关于checkpoint的信息只会被写到日志文件组的第一个日志文件的管理信息中
- 3.当checkpoint_no的值是偶数时,就写到checkpoint1中,是奇数时,就写到checkpoint2中
批量从flush链表中刷出脏页
- 1.一般情况下都是后台的线程在对LRU链表和flush链表进行刷脏操作
- 2.如果后台的刷脏操作不能将脏页刷出,那么系统无法及时做checkpoint,可能就需要用户线程同步的从flush链表中把那些最早修改的脏页(oldest_modification最小的脏页)刷新到磁
- 3.上述情况导致这些脏页对应的redo日志就没用了,然后就可以去做checkpoint了。
查看系统中的各种LSN值
- 1.使用SHOW ENGINE INNODB STATUS命令查看当前InnoDB存储引擎中的各种LSN值的情况
- 2.Log sequence number:代表系统中的lsn值,也就是当前系统已经写入的redo日志量,包括写入log buffer中的日志
- 3.Log flushed up to:代表flushed_to_disk_lsn的值,也就是当前系统已经写入磁盘的redo日志量
- 4.Pages flushed up to:代表flush链表中被最早修改的那个页面对应的oldest_modification属性值
- 5.Last checkpoint at:当前系统的checkpoint_lsn值。
innodb_flush_log_at_trx_commit
- 1.为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有redo日志都刷新到磁盘上
- 2.但是1会降低数据库性能,如果对事务的持久性要求不是那么清冽可以修改innodb_flush_log_at_trx_commit
- 3.innodb_flush_log_at_trx_commit=0:当该系统变量值为0时,表示在事务提交时不立即向磁盘中同步redo日志,这个任务是交给后台线程做的
- 4.innodb_flush_log_at_trx_commit=1(默认):表示在事务提交时需要将redo日志同步到磁盘,可以保证事务的持久性
- 5.innodb_flush_log_at_trx_commit=2:表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘,需要OS自己控制
崩溃恢复
确定恢复的起点
- 1.checkpoint_lsn之后的都是(有可能)未刷入磁盘的,所以需要解析这部分redo日志
- 2.redo日志文件组的第一个文件的管理信息中有两个block都存储了checkpoint_lsn的信息,我们当然是要选取最近发生的那次checkpoint的信息--通过checkpoint_no比较
- 3.这样我们就能拿到最近发生的checkpoint对应的checkpoint_lsn值以及它在redo日志文件组中的偏移量checkpoint_offset。
确定恢复的终点
- 1.普通block的log block header部分有一个称之为LOG_BLOCK_HDR_DATA_LEN的属性,该属性值记录了当前block里使用了多少字节的空间
- 2.对于被填满的block来说,该值永远为512。如果该属性的值不为512,那么就是它了,它就是此次奔溃恢复中需要扫描的最后一个block。
怎么恢复
- 1.使用哈希表:根据redo日志的space ID和page number属性计算出散列值,把space ID和page number相同的redo日志放到哈希表的同一个槽里
,如果有多个space ID和page number都相同的redo日志,那么它们之间使用链表连接起来,按照生成的先后顺序链接起来的 - 2.之后就可以遍历哈希表,因为对同一个页面进行修改的redo日志都放在了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机IO),这样可以加快恢复速度
- 3.另外需要注意一点的是,同一个页面的redo日志是按照生成时间顺序进行排序的,所以恢复的时候也是按照这个顺序进行恢复,如果不按照生成时间顺序进行排序的话,那么可能出现错误。比如原先的修改操作是先插入一条记录,再删除该条记录
,如果恢复时不按照这个顺序来,就可能变成先删除一条记录,再插入一条记录,这显然是错误的 - 4.跳过已经刷新到磁盘的页面:因为checkpoint_lsn之后的redo日志我们不能确定是否已经刷到磁盘
- 5.通过页面的File Header的FIL_PAGE_LSN属性:记载了最近一次修改页面时对应的lsn值(其实就是页面控制块中的newest_modification值)
- 6.如果在做了某次checkpoint之后有脏页被刷新到磁盘中,那么该页对应的FIL_PAGE_LSN代表的lsn值肯定大于checkpoint_lsn的值,凡是符合这种情况的页面就不需要重复执行lsn值小于FIL_PAGE_LSN的redo日志了
恢复策略
- 1.前面说到未提交的事务和回滚了的事务也会记录Redo Log,因此在进行恢复时,这些事务要进行特殊的
的处理.有2中不同的恢复策略:
A. 进行恢复时,只重做已经提交了的事务。
B. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过Undo Log回滚那些未提交的事务。 - 2.innodb选择B策略
LOG_BLOCK_HDR_NO是如何计算的
- 1.在log block header处有一个称之为LOG_BLOCK_HDR_NO的属性
- 2.这个属性代表一个唯一的标号。这个属性是初次使用该block时分配的,跟当时的系统lsn值有关
- 3.具体公司如下:((lsn / 512) & 0x3FFFFFFFUL) + 1,0x3FFFFFFFUL代表32位比特中前两位是0后30位是1
- 4.让一个数和0x3FFFFFFFUL做与运算的意思就是要将该值的前2个比特位的值置为0,这样该值就肯定小于或等于0x3FFFFFFFUL了
- 5.这也就说明了,不论lsn多大,((lsn / 512) & 0x3FFFFFFFUL)的值肯定在0~0x3FFFFFFFUL之间
- 6.LOG_BLOCK_HDR_NO的第一个比特位比较特殊,成为flushbit,如果为1代表本block是在某次将log buffer中的block刷新到磁盘的操作中的第一个被刷入的block