IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

InnoDB Log 漫游(2)

MySQLOPS 数据库与运维自动化技术分享 2012-04-07 14:37:12 累计浏览 2,353 次
本机暂存

03 - 日志的内容

- 数据是什么
  从不同的角度和层次来看,我们可以将数据库中的数据看作:
  A. 关系数据
  B. 元组或对象
  C. 存在Page中的二进制序列

  因此Log中也可以记录不同的内容:
- 物理的日志(Physical Log)
  A. 记录完整的Page
  B. 记录Page中被修改的部分(page中的偏移,内容和长度).

  优点:因为恢复时,完全不依赖原页面上的内容,所以不要求持久化了的数据保持在一个一致的状态。
       比如在写一个页面到磁盘上时,系统发生故障,页面上的一部数据写入了磁盘,另一部分丢失了。
       这时仍然可以恢复出正确的数据。

  缺点:Log记录的内容很多,占用很大的空间。如B-Tree的分裂操作,要记录约一个完整Page的内容。

- 逻辑的日志(Logical Log)
  记录在关系(表)上的一个元组操作。
  A. 插入一行记录。
  B. 修改一行记录。
  C. 删除一行记录。
  逻辑日志比起物理的日志,显得简洁的多。而且占用的空间也要小的多。
  但是逻辑日志有2个缺点:
  A. 部分执行
     例如:表T有2个索引,在向T插入1条记录时,需要分别向2个B-Tree中插入记录。
     有可能第一个B-Tree插入成功了,但是第二个B-Tree没有插入成功。在恢复或
     回滚时,需要处理这些特殊情况。
  B. 操作的一致性问题
     一个插入操作有一个B-Tree的分裂,页A的一半数据移到了B页,A页写入了磁盘,B页没有写入磁盘。
     如果这时候发生了故障,需要进行恢复,逻辑日志是很难搞定的。

  逻辑的日志上的‘部分执行’的问题是比较好维护的,但是‘一致性’的问题维护起来是很复杂的。

- 物理和逻辑结合的日志(Physiological Log)
  这种日志将物理和逻辑日志相结合,取其利,去其害。从而达到一个相对更好的一个状态。这种日志有2个特点:
  A. 物理到page. 将操作细分到页级别。为每个页上的操作单独记日志。
     比如,一个Insert分别在2个B-Tree的节点上做了插入操作,那么就分别为每一个页的操作记录一条日志。
  B. Page内采用逻辑的日志。比如对一个B-Tree的页内插入一条记录时,物理上来说要修改Page Header的
     内容(如,页内的记录数要加1),要插入一行数据到某个位置,要修改相邻记录里的链表指针,要修改Slot的
     属性等。从逻辑上来说,就是在这个页内插入了一行记录。因此Page内的逻辑日志只记录:’这是一个
     插入操作’和’这行数据的内容‘。

  MySQL数据库InnoDB存储引擎的Redo Log 记录的就是这种物理和逻辑相结合的日志。
  使用页内的逻辑日志,可以减少日志占用的空间。但是它毕竟还是逻辑日志,上面提到的2个问题能够避免吗?
  A. 页面内的部分执行的情况可以认为不存在了。因为整个页面的操作是原子操作,在完成之前是不会写
     到磁盘上的。
  B. 操作一致性的问题仍然存在。如果在写一个Page到磁盘时发生了故障,可能导致Page Header的记
     录数被加1了,但是数据没有刷新到磁盘上,总之页面上的数据不一致了。

  好在这个问题被缩小到了一个页面的范围内,因此比较容易解决。InnoDB存储引擎中用Double Write的方法
  来解决这个问题。

- Double Write
  Double Write的思路很简单:
  A. 在覆盖磁盘上的数据前,先将Page的内容写入到磁盘上的其他地方(InnoDB存储引擎中的doublewrite
     buffer,这里的buffer不是内存空间,是持久存储上的空间).
  B. 然后再将Page的内容覆盖到磁盘上原来的数据。

  如果在A步骤时系统故障,原来的数据没有被覆盖,还是完整的。
  如果在B步骤时系统故障,原来的数据不完整了,但是新数据已经被完整的写入了doublewrite buffer.
  因此系统恢复时就可以用doublewrite buffer中的新Page来覆盖这个不完整的page.

  Double write 显然会曾加磁盘的IO。直觉上IO次数增加了1倍,但是性能损失并不是很大。Peter在
  innodb-double-write中说性能损失不超过5-10%。应该是因为多数情况下使用了批量写入的缘故。
  A. Double write buffer是一段连续的存储空间,可以顺序写入。
  B. Double write有自己的写buffer.
  C. 先将多个要做doublewrite的page写入内存的buffer,然后再一起写到磁盘上。

  代码在:buf0dblwr.cc
  buf_flush_write_block_low()调用
  buf_dblwr_write_single_page()或 buf_dblwr_add_to_batch()来实现doublewrite.

- Checksum
  检测页面是否一致的功能是靠Checksum来完成的,每个页面修改完成后都会记算一个页面的checksum。
  这个checksum存放在页面的尾部.每次从磁盘读一个页到内存时,都需要检测页的一致性。
  函数buf_page_is_corrupted()是用来检测page的一致性的.

- InnoDB Redo Log的日志类型
  InnoDB redo log的格式可以概括为:
  <Space ID>+<Page NO.>+<操作类型>+<数据>.

  Redo Log记录的页面操作大致可以分为以下几种类型:
  A. 在页面上写入N个字节的内容,这些可以看作是物理的Log.
     MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES, MLOG_8BYTES, MLOG_WRITE_STRING
     各种Page链表的指针修改,以及文件头,段页等的内容的修改都是以这种方式记录的日志。
  B. 页面上的记录操作。
     MLOG_REC_*, MLOG_LIST_*, MLOG_COMP_REC_*, MLOG_COMP_LIST_*
     这些日志记录了对B-Tree页的INSER, DELETE, UPDATE操作和分裂合并操作。
  C. 文件和Page操作
     MLOG_FILE_CREATE, MLOG_FILE_RENAME, MLOG_FILE_DELETE,
     MLOG_PAGE_CREATE, MLOG_INIT_FILE_PAGE, MLOG_PAGE_REORGANIZE
  D. Undo Log操作
     MLOG_UNDO_*
     InnoDB中将undo log的操作也记入了redo log. 为什么要这样做,在前面‘恢复’已经说了.

  这里只提到了部分Redo Log的类型,完整的定义在mtr0mtr.h文件中. 通过这个类型的定义,可以
  很容易的找到都在哪些地方使用了。

  虽说Redo Log将数据的操作细分到了页面级别。但是有些在多个页面上的操作是逻辑上不可分裂的。
  比如B-Tree的分裂操作,对父节点和2个子节点的修改。当进行恢复时,要么全部恢复,要么全部不
  恢复,不能只恢复其中的部分页面。InnoDB中通过mini-transaction(MTR)来保证这些不可再分
  的操作的原子性。

- InnoDB Undo Log的日志类型
  MySQL数据库InnoDB存储引擎的undo log采用了逻辑的日志。
  InnoDB undo log的格式可以概括为:<操作类型>+<Table ID>+<数据>.

  A. 从表中删除一行记录
     TRX_UNDO_DEL_MARK_REC(将主键记入日志)
     在删除一条记录时,并不是真正的将数据从数据库中删除,只是标记为已删除.这样做的好处是
     Undo Log中不用记录整行的信息.在undo时操作也变得很简单.
  B. 向表中插入一行记录
     TRX_UNDO_INSERT_REC(将主键记入日志)
     TRX_UNDO_UPD_DEL_REC(紧将主键记入日志) 当表中有一条被标记为删除的记录和要插入的
     数据主键相同时, 实际的操作是更新这个被标记为删除的记录。
  C. 更新表中的一条记录
     TRX_UNDO_UPD_EXIST_REC(将主键和被更新了的字段内容记入日志)
     TRX_UNDO_DEL_MARK_REC和TRX_UNDO_INSERT_REC,当更新主键时,实际执行的过程
     是删除旧的记录然后,再插入一条新的记录。

  因为undo log还要被MVCC和Purge使用,所以还有TRX_ID和DATA_ROLL_PTR等特殊的内容记录
  在日志中。TRX_UNDO_INSERT_REC不需要记录这些内容.因为MVCC中不可内引用一个不存在的数据。
  这也是事务将insert和update、delete的undo log分开存放的原因。事务提交后,insert的undo
  占用的空间就可以立即释放了.

  这些类型定义在:trx0rec.h.
  记录日志的过程在:trx_undo_page_report_insert()和trx_undo_page_report_modify()中。
  Undo操作在row0undo.c, row0uins.c和row0umod.c中, 入口函数是row_undo().

- 逻辑日志的一致性问题
  前面说了逻辑日志的一致性问题是很复杂的,为什么undo log要用逻辑日志呢?
  因为redo log使用了physiological日志和MTR,就可以保证在恢复时重做完redo log后,
  数据是一致。在执行undo时,就不必考虑这个问题了。

同分类推荐文章

  1. 使用deepseek进行Oracle恢复,引起重大故障 (2026-06-22 10:56:00)
  2. 接手一个只差临门一脚的数据库恢复 (2026-06-18 00:13:09)
  3. 我做了一个 AI 版的 StarRocks 升级风险扫描工具,直接帮我定位到一个风险 (2026-06-15 01:00:00)

查看更多 数据库 文章 →

建议继续学习

  1. 深入浅出INNODB MVCC机制与原理 (累计阅读 9,693)
  2. Innodb分表太多或者表分区太多,会导致内存耗尽而宕机 (累计阅读 7,721)
  3. Innodb IO优化-配置优化 (累计阅读 7,728)
  4. 由浅入深理解索引的实现(2) (累计阅读 7,711)
  5. MySQL中like语句及相关优化器tips (累计阅读 6,282)
  6. Innodb 表和索引结构 (累计阅读 6,226)
  7. InnODB和MyISAM索引统计集合 (累计阅读 6,235)
  8. 从load data引发的死锁说起 (累计阅读 6,143)
  9. 一次神奇的MySQL优化 (累计阅读 6,082)
  10. InnoDB线程并发检查机制 (累计阅读 5,796)