image.png

  • type:该条 redo 日志的类型,redo 日志设计大约有 53 种不同的类型日志。
  • space ID:表空间 ID。
  • page number:页号。
  • data:该条 redo 日志的具体内容。


简单的redo日志

如果我们没有为某个表显式的定义主键,并且表中也没有定义 Unique 键,那么 InnoDB 会自动的为表添加一个称之为 row_id 的隐藏列作为主键。为这个 row_id 隐藏列赋值的方式如下:
服务器会在内存中维护一个全局变量,每当向某个包含隐藏的 row_id 列的表中插入一条记录时,就会把该变量的值当作新记录的 row_id 列的值,并且把该变量自增 1。
每当这个变量的值为 256 的倍数时,就会将该变量的值刷新到系统表空间的页号为 7 的页面中一个称之为 Max Row ID 的属性处。
当系统启动时,会将上边提到的 Max Row ID 属性加载到内存中,将该值加上 256 之后赋值给我们前边提到的全局变量。
这个 Max Row ID 属性占用的存储空间是 8 个字节,当某个事务向某个包含row_id 隐藏列的表插入一条记录,并且为该记录分配的 row_id 值为 256 的倍数时,就会向系统表空间页号为 7 的页面的相应偏移量处写入 8 个字节的值。但是我们要知道,这个写入实际上是在 Buffer Pool 中完成的,我们需要为这个页面的修改记录一条 redo 日志,以便在系统崩溃后能将已经提交的该事务对该页面所做的修改恢复出来。这种情况下对页面的修改是极其简单的,redo **日志中只需要记录一下在某个页面的某个偏移量处修改了几个字节的值,具体被修改的内容是什么,InnoDB 把这种极其简单的 redo 日志称之为物理日志。
image.png**
复杂的redo日志

有时候执行一条语句会修改非常多的页面,包括系统数据页面和用户数据页面(用户数据指的就是聚簇索引和二级索引对应的 B+树)。
在语句执行过程中,INSERT 语句对所有页面的修改都得保存到 redo 日志中去。实现起来是非常麻烦的,比方说将记录插入到聚簇索引中时,如果定位到的叶子节点的剩余空间足够存储该记录时,那么只更新该叶子节点页面就好,那么只记录一条 MLOG_WRITE_STRING 类型的 redo 日志,表明在页面的某个偏移量处增加了哪些数据就好了么?
别忘了一个数据页中除了存储实际的记录之后,还有什么 File Header、Page Header、Page Directory 等等部分,所以每往叶子节点代表的数据页里插入一条记录时,还有其他很多地方会跟着更新,比如说:
可能更新 Page Directory 中的槽信息、Page Header 中的各种页面统计信息, 比如槽数量可能会更改,还未使用的空间最小地址可能会更改,本页面中的记录数量可能会更改,各种信息都可能会被修改,同时数据页里的记录是按照索引列从小到大的顺序组成一个单向链表的,每插入一条记录,还需要更新上一条记录的记录头信息中的 next_record 属性来维护这个单向链表。
画一个简易的示意图就像是这样:
image.png
其实说到底,把一条记录插入到一个页面时需要更改的地方非常多。这时我们如果使用上边介绍的简单的物理 redo 日志来记录这些修改时,可以有两种解决方案:
方案一:在每个修改的地方都记录一条 redo 日志。
也就是如上图所示,有多少个加粗的块,就写多少条物理 redo 日志。这样子记录 redo 日志的缺点是显而易见的,因为被修改的地方是在太多了,可能记录的 redo 日志占用的空间都比整个页面占用的空间都多了。
方案二:将整个页面的第一个被修改的字节到最后一个修改的字节之间所有的数据当成是一条物理 redo 日志中的具体数据。
从图中也可以看出来,第一个被修改的字节到最后一个修改的字节之间仍然有许多没有修改过的数据,我们把这些没有修改的数据也加入到 redo 日志中去依然很浪费。
正因为上述两种使用物理 redo 日志的方式来记录某个页面中做了哪些修改比较浪费,InnoDB 中就有非常多的 redo 日志类型来做记录。
这些类型的 redo 日志既包含物理层面的意思,也包含逻辑层面的意思,具体指:

  • 物理层面看,这些日志都指明了对哪个表空间的哪个页进行了修改。
  • 逻辑层面看,在系统崩溃重启时,并不能直接根据这些日志里的记载,将页面内的某个偏移量处恢复成某个数据,而是需要调用一些事先准备好的函数,执行完这些函数后才可以将页面恢复成系统崩溃前的样子。

简单来说,一个 redo 日志类型而只是把在本页面中变动(比如插入、修改) 一条记录所有必备的要素记了下来,之后系统崩溃重启时,服务器会调用相关向某个页面变动(比如插入、修改)一条记录的那个函数,而 redo 日志中的那些数据就可以被当成是调用这个函数所需的参数,在调用完该函数后,页面中的相关值也就都被恢复到系统崩溃前的样子了。这就是所谓的逻辑日志的意思。
当然,如果不是为了写一个解析 redo 日志的工具或者自己开发一套 redo 日志系统的话,那就不需要去研究 InnoDB 中的 redo 日志具体格式。
只要记住:redo 日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。