炫酷的html5网站,百度识图扫一扫,手机网站设计需要学什么,自己创建网站403redo 日志
什么是redo日志#xff1f;在说这个之前我们先来想一个场景#xff0c;在访问磁盘的页面之前#xff0c;我们会先把页面缓存到Buffer Pool之后#xff0c;才会访问。写页面的时候也会先将buffer pool中的页面修改之后#xff0c;然后在某个时机才会刷新到磁盘中…redo 日志
什么是redo日志在说这个之前我们先来想一个场景在访问磁盘的页面之前我们会先把页面缓存到Buffer Pool之后才会访问。写页面的时候也会先将buffer pool中的页面修改之后然后在某个时机才会刷新到磁盘中。这个时候就有个问题我们知道 InnoDB 是支持事务的假设我们提交了一个修改的事务但是事务提交之后系统突然发生故障了导致内存数据丢失了那我们事务的持久性要怎么做保证呢最简单的做法是事务提交完成前就把事务所修改的页面都刷新到磁盘这样就不会有问题但是有两个问题 刷新一个完整的数据页太浪费资源因为有可能一个数据页只更新了一个数据然后就要刷新整个数据页性价比太低了事务可能包含了很多修改语句语句可能修改了很多页面这些页面不一定是顺序存储的所以我们随机IO 刷新数据页会特别慢 所以上面那种方案性价比太低了回顾整个场景我们其实要解决的问题无非就是把修改的操作记录下来并且事务提交之后永久生效嘛即使系统崩溃了也能及时修复而存储事务对数据库修改的日志文件被称之为 redo log使用 redo 日志的优点在于 redo 日志占用空间小redo 日志刷新磁盘是顺序IO
redo 日志格式 redo 日志的本质是记录了一下事务对数据库做了哪些修改所以 redo 日志都会有下面这些通用的结构 type该条redo日志类型 MLOG_1BYTEtype字段对应的十进制数字为1表示在页面的某个偏移量处写入1个字节的redo日志类型。 MLOG_2BYTEtype字段对应的十进制数字为2表示在页面的某个偏移量处写入2个字节的redo日志类型。 MLOG_4BYTEtype字段对应的十进制数字为4表示在页面的某个偏移量处写入4个字节的redo日志类型。 MLOG_8BYTEtype字段对应的十进制数字为8表示在页面的某个偏移量处写入8个字节的redo日志类型。 当 type 类型为这个类型时会多一个参数offset MLOG_WRITE_STRINGtype字段对应的十进制数字为30表示在页面的某个偏移量处写入一串数据。 当 type 类型为这个类型时会多两个参数offset len space ID表空间ID page number页号 data该条redo 日志的具体内容
复杂的 redo 日志类型
有时候执行一条语句会修改很多东西比如一条insert 语句会更新许多 B 树对于一颗 B 树可能又会更新很多节点那这个时候就有个疑问了假设我们执行一条语句会更新很多地方比如下图 我们需不需要把这些更新信息都保存下来呢毕竟把一条记录插入到一个页面需要更改的地方存储下来的空间可能比单纯存这条记录都要大所以要怎么存储才合适呢这个时候我们就需要引入一些新的 type 类型这些 type 类型又有配套的 函数而我们只需要存储这些函数需要的参数就可以了我们举个例子类型为 MLOG_COMP_REC_INSERT ,代表插入一条使用紧凑行格式的记录先来看一下这个日志类型的结构 其中 n_uniques 代表这条记录的唯一值field1_len ~ fieldn_len代表着该记录若干个字段占用存储空间的大小offset代表的是该记录的前一条记录在页面中的地址等等根据这些参数在恢复的时候调用这个函数就可以将数据恢复到系统崩溃前的样子
Mini-Transaction
redo 日志的更新是根据组来更新的而这种方式被称之为 Mini-Transaction 为什么会需要这样的呢这个时候就不得不说一个场景当你往一个有空闲空间的页插入一条数据更改的redo记录会比较少我们基本上使用一行就能解决叫做乐观插入但是假设你往一个已经满空间的页插入一条数据则会产生页分裂也就是新建一个叶子节点然后把原先数据页中的一部分记录复制到这个新的数据页中然后再把记录插入进去把这个叶子节点插入到叶子节点链表中最后还要在内节点中添加一条目录项记录指向这个新创建的页面。很明显上述操作会产生多条redo日志但是这个操作必须是原子性的不能说插入一般就停止了所以就规定执行这些需要保证原子性的操作时就必须以组的形式来记录redo日志理解之后我们就来思考如何把这些redo日志划分到一个组里边呢设计InnoDB的大佬做了一个很简单的小把戏就是在该组中的最后一条redo日志后边加上一条特殊类型的redo日志该类型名称为MLOG_MULTI_REC_ENDtype字段对应的十进制数字为31该类型的redo日志结构很简单只有一个type字段所以某个需要保证原子性的操作产生的一系列redo日志必须要以一个类型为MLOG_MULTI_REC_END结尾就像这样这样在系统奔溃重启进行恢复时只有当解析到类型为MLOG_MULTI_REC_END的redo日志才认为解析到了一组完整的redo日志才会进行恢复。否则的话直接放弃前面解析到的redo日志。但是有些原子性操作只生成了一条redo日志后面如果加上一个MLOG_MULTI_REC_END的redo日志会不会太过浪费了所以设计InnoDB的大佬type的第一个比特位作为判断如果type字段的第一个比特位为1代表该需要保证原子性的操作只产生了单一的一条redo日志否则表示该需要保证原子性的操作产生了一系列的redo日志。了解完大致之后我们需要知道什么时机下产生的redo日志被设计InnoDB的大佬人为的划分成了若干个不可分割的组 更新Max Row ID属性时产生的redo日志是不可分割的。向聚簇索引对应B树的页面中插入一条记录时产生的redo日志是不可分割的。向某个二级索引对应B树的页面中插入一条记录时产生的redo日志是不可分割的还有其他的一些对页面的访问操作时产生的redo日志是不可分割的 顺便总结一下 Mini-Transaction 的概念设计MySQL的大佬把对底层页面中的一次原子访问的过程称之为一个Mini-Transaction简称mtr
redo 日志的写入过程 redo log blockredo 日志存储单元都是一个个页 log block header: LOG_BLOCK_HDR_NO每一个block都有一个大于0的唯一标号本属性就表示该标号值LOG_BLOCK_HDR_DATA_LEN表示block中已经使用了多少字节初始值为12因为log block body从第12个字节处开始。随着往block中写入的redo日志越来也多本属性值也跟着增长。如果log block body已经被全部写满那么本属性的值被设置为512。LOG_BLOCK_FIRST_REC_GROUP一条redo日志也可以称之为一条redo日志记录redo log record一个mtr会生产多条redo日志记录这些redo日志记录被称之为一个redo日志记录组redo log record groupLOG_BLOCK_FIRST_REC_GROUP就代表该block中第一个mtr生成的redo日志记录组的偏移量其实也就是这个block里第一个mtr生成的第一条redo日志的偏移量。LOG_BLOCK_CHECKPOINT_NO表示所谓的checkpoint的序号checkpoint是我们后续内容的重点现在先不用清楚它的意思稍安勿躁。 redo 日志缓冲区设计InnoDB的大佬为了解决磁盘速度过慢的问题而引入了Buffer Pool。同理写入redo日志时也不能直接直接写到磁盘上实际上在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间翻译成中文就是redo日志缓冲区我们也可以简称为log buffer。这片内存空间被划分成若干个连续的redo log block就像这样 log buffer
redo 日志文件
刷机时机 log buffer 空间不足的时候事务提交的时候后台线程每秒异步刷新一次 log buffer 到 redo 日志到磁盘正常关闭服务器的时候checkpoint 的时候 日志文件组redo 日志文件在磁盘上是不止一个的而是以一个文件组的方式出现的文件名是以ib_logfile为前缀数字为后缀进行命名的例如 ib_logfile0,ib_logfile1默认情况下会创建这两个文件但是我们可以通过一些参数来调整文件的数量和大小 innodb_log_group_home_dir该参数指定了redo日志文件所在的目录默认值就是当前的数据目录innodb_log_file_size该参数指定了每个redo日志文件的大小在MySQL 5.7.21这个版本中的默认值为48MBinnodb_log_files_in_group该参数指定redo日志文件的个数默认值为2最大值为100。磁盘上redo日志文件的大小就是 innodb_log_file_size × innodb_log_files_in_group 日志文件格式被划分为 512 个字节大小的block其实就是由若干个512字节大小的block组成分为两部分组成 前2048个字节存储一些管理信息 前4个block分别为什么 log file header描述该redo日志文件的一些整体属性 LOG_HEADER_FORMAT 4字节 redo日志的版本在MySQL 5.7.21中该值永远为1LOG_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都有我们不关心 checkpoint1记录关于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操作时对应的log buffer的大小LOG_BLOCK_CHECKSUM 4字节 本block的校验值所有block都有我们不关心 checkpoint2结构和checkpoint1一样 从2048个字节开始就是用来存储block数据 Log Sequeue Number日志序列号初始值为8704随着插入日志一直增长 系统第一次启动后初始化 log buffer 时就会指向第一个block 的偏移量为12字节的地方然后lsn就会随之增加870412 8716如果当前待插入的block空间可以容纳即将插入mtr提交的日志lsn的增长值就应该是 mtr 生成 redo 日志占用的字节数8716200 8916如果某个mtr产生的一组redo日志占用的存储空间比较大也就是待插入的block剩余空闲空间不足以容纳这个mtr提交的日志时lsn增长的量就是该mtr生成的redo日志占用的字节数加上额外占用的log block header和log block trailer的字节数8916100012242 9948每一组由mtr生成的redo日志都有一个唯一的LSN值与其对应LSN值越小说明redo日志产生的越早 flushed_to_disk_lsnredo日志是首先写到log buffer中之后才会被刷新到磁盘上的redo日志文件。所以设计InnoDB的大佬提出了一个称之为buf_next_to_write的全局变量标记当前log buffer中已经有哪些日志被刷新到磁盘中了。 系统初始化的时候flushed_to_disk_lsn 的值跟 lsn 是一样的当有新的redo日志写入到log buffer时首先lsn的值会增长但flushed_to_disk_lsn不变随后随着不断有log buffer中的日志被刷新到磁盘上flushed_to_disk_lsn的值也跟着增长。如果两者的值相同时说明log buffer中的所有redo日志都已经刷新到磁盘中了。 lsn值和redo日志文件偏移量的对应关系 因为lsn的值是代表系统写入的redo日志量的一个总和一个mtr中产生多少日志lsn的值就增加多少当然有时候要加上log block header和log block trailer的大小这样mtr产生的日志写到磁盘中时很容易计算某一个lsn值在redo日志文件组中的偏移量flush链表中的LSN 在mtr执行过程中可能修改过的页面加入到Buffer Pool的flush链表flush缓存页中的控制块记录两个关于页面何时修改的属性 oldest_modification如果某个页面被加载到Buffer Pool后进行第一次修改那么就将修改该页面的mtr开始时对应的lsn值写入这个属性。newest_modification每修改一次页面都会将修改该页面的mtr结束时对应的lsn值写入这个属性。也就是说该属性表示页面最近一次修改后对应的系统lsn值。 flush链表中的脏页按照修改发生的时间顺序进行排序也就是按照oldest_modification代表的LSN值进行排序被多次更新的页面不会重复插入到flush链表中但是会更新newest_modification属性的值。
checkpoint
有一个很不幸的事实就是我们的redo日志文件组容量是有限的我们不得不选择循环使用redo日志文件组中的文件但是这会造成最后写的redo日志与最开始写的redo日志追尾这时应该想到redo日志只是为了系统奔溃后恢复脏页用的如果对应的脏页已经刷新到了磁盘也就是说即使现在系统奔溃那么在重启后也用不着使用redo日志恢复该页面了所以该redo日志也就没有存在的必要了那么它占用的磁盘空间就可以被后续的redo日志所重用。也就是说判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里。这边举个例子来说明现在页a被刷新到了磁盘mtr_1生成的redo日志就可以给覆盖了所以进行一个增加checkpoint_lsn操作 步骤一计算一下当前系统中可以被覆盖的redo日志对应的lsn值最大是多少redo日志可以被覆盖意味着它对应的脏页被刷到了磁盘只要我们计算出当前系统中被最早修改的脏页对应的oldest_modification值那凡是在系统lsn值小于该节点的oldest_modification值时产生的redo日志都是可以被覆盖掉的我们就把该脏页的oldest_modification赋值给checkpoint_lsn。 比方说当前系统中页a已经被刷新到磁盘那么flush链表的尾节点就是页c该节点就是当前系统中最早修改的脏页了它的oldest_modification值为8916我们就把8916赋值给checkpoint_lsn也就是说在redo日志对应的lsn值小于8916时就可以被覆盖掉。 步骤二将checkpoint_lsn和对应的redo日志文件组偏移量以及此次checkpint的编号写到日志文件的管理信息就是checkpoint1或者checkpoint2中。 设计InnoDB的大佬维护了一个目前系统做了多少次checkpoint的变量checkpoint_no每做一次checkpoint该变量的值就加1。我们前面说过计算一个lsn值对应的redo日志文件组偏移量是很容易的所以可以计算得到该checkpoint_lsn在redo日志文件组中对应的偏移量checkpoint_offset然后把这三个值都写到redo日志文件组的管理信息中。我们说过每一个redo日志文件都有2048个字节的管理信息但是上述关于checkpoint的信息只会被写到日志文件组的第一个日志文件的管理信息中。不过我们是存储到checkpoint1中还是checkpoint2中呢设计InnoDB的大佬规定当checkpoint_no的值是偶数时就写到checkpoint1中是奇数时就写到checkpoint2中。
崩溃恢复
确定恢复的起点
从checkpoint_lsn 开始读取redo日志来恢复页面衡量checkpoint发生时间早晚的信息就是所谓的checkpoint_no我们只要把checkpoint1和checkpoint2这两个block中的checkpoint_no值读出来比一下大小哪个的checkpoint_no值更大说明哪个block存储的就是最近的一次checkpoint信息。这样我们就能拿到最近发生的checkpoint对应的checkpoint_lsn值以及它在redo日志文件组中的偏移量checkpoint_offset。
确定恢复的终点
普通block的log block header部分有一个称之为LOG_BLOCK_HDR_DATA_LEN的属性该属性值记录了当前block里使用了多少字节的空间。对于被填满的block来说该值永远为512。如果该属性的值不为512那么就是它了它就是此次奔溃恢复中需要扫描的最后一个block。
总结待更新