[翻译]日志在MySQL InnoDB表中是如何工作的

翻译自 How Logs Work On MySQL With InnoDB Tables
原文链接

By: Peter Gulutzan
August 06, 2002

在本文中,我将描述日志在MySQL和InnoDB中是如何工作的。在手册中很少有相关的资料。这些是在我研究我们的新的书时候(SQL性能调优 by GPeter Gulutzan and Trudy Pelzer),从源代码收集到的信息。

如果你曾经使用过MySQL和InnoDB表,本文会给你一些其他地方找不到的“内幕”知识,所以请坐下来准备阅读吧!

当执行数据修改语句时会发生什么?

当你用更新、插入或删除请求改变数据的时候,你将在两个地方改变数据:日志缓冲和数据缓冲。缓冲是有固定长度的,通常512字节的倍数,它们保存在内存中——InnoDB还没有把他们写入磁盘。

LOG BUFFER(日志缓冲)   DATA BUFFER(数据缓冲)
=================        ===============
= Log Record #1 =        = Page Header =
= Log Record #2 =        = Data Row    =
= Log Record #3 =        = Data Row    =
= Log Record #4 =        = Data Row    =
=================        ===============

例如,在“INSERT INTO Jobs VALUES (1,2,3)“之后,日志缓冲将会有一个新的日志记录——称之为 “Log Record #5” 包含一个行标识符和这行的新内容。与此同时,数据缓冲将有一个新行,但它也将有一个在页面头部写入“这个页面的最新日志记录是 Log Record #5”。在这个例子中“#5”表示日志序列号(LSN),这对后面的调度操作是至关重要的。

一些关于数据修改的详细信息:
(a)插入日志记录只包含新的数据,这就足够了。这个过程在必要时可以在同一页重做。这就是所谓的“重做”条目。
(b)LSN不是日志记录的一个字段,而是,它是文件和字节偏移量的绝对地址。

InnoDB后改变了日志缓冲和缓冲的数据,一切都结束了,但磁盘还在写入。这正是事情变得复杂地方。有几个线程监控缓冲活动和并且有三种情况:溢出,Checkpoint和提交--这些情况会导致磁盘写。

当溢出时会发生什么?

溢出是罕见的,因为InnoDB采取积极的措施以防止缓冲用完(参见下面的“当Checkpoint时会发生什么”)。不过,让我们来讨论两种可能的情况。

情况一:如果日志缓冲满了,InnoD会在缓冲“结束”的地方写日志。我把“结束”一词写在引号里是因为一个日志文件,或更准确说的一组日志文件,看起来像一条蛇吞下它的尾巴。假设空间只够写四个日志记录,当我们写 #5的时候,那么它必须写在文件的开始。

写日志记录#5之前的日志文件
=================
= Log Record #1 =
= Log Record #2 =
= Log Record #3 =
= Log Record #4 =
=================
写日志记录#5之后的日志文件
=================
= Log Record #5 =
= Log Record #2 =
= Log Record #3 =
= Log Record #4 =
=================

不会有日志一直增长的情况出现。尽管InnoDB使用一些压缩技巧,日志文件也不会太大以至于任何磁盘驱动器都写不下。由于InnoDB会“循环”写入,这意味着它必须覆盖旧的日志记录。这个循环日志记录策意味着之后会再从这个地方写。

情况二:如果数据缓冲满了,InnoDB会将最近最少使用的缓冲写入数据库——但不是很快发生!这是页面头部里的LSN变得有用的地方。首先,InnoDB检查头部的LSN是否大于日志文件中的LSN。如果是大于,那么InnoDB必须先写日志,直到日志赶上数据页头部的LSN,才可以写入数据。换句话说数据页不会写入,直到已经写入相应的日志。这是常见的“写前先记日志”的原则,这对所有重要的DBMS来说都是通用的,除了InterBase。

当Checkpoint时会发生什么?

我之前说过InnoDB会对溢出采取一些积极的措施,其中最重要的措施是Checkpoint。有一个单独的线程,或者说独立于change buffer的联合线程。在固定时间间隔检Checkpoint线程会唤醒,查看缓冲的变化,确保发生写入。

据我所知,此时大多数DBMS的会把所有的东西都写入,这时就没有已改变但还没有写入的缓冲存在了。用常用的术语来说,DBMS将用“Sharp Checkpoint”Flush掉所有的“脏”缓冲。但InnoDB只确保:(a)日志和数据缓冲没有达到固定阈值点,(b)写数据页之前先写日志,(c)数据缓冲的页面头部的LSN对应的日志记录不会被覆盖。用行话来说,这意味着InnoDB是一个“Fuzzy Checkpoint”的粉丝。

在Checkpoint发生的时候,可能会写另一个日志记录说:当这个Checkpoint发生的时候,它是确定数据库是最新的,除了几个脏页,这是脏页的列表。当发生恢复过程的时候,这个信息可能非常有用,所以我之后会再次提到它。

当提交时会发生什么?

当提交时,InnoDB不会把所有脏数据页写到磁盘上。我之所以强调这一点,是因为很容易认为提交更改意味着将所有的变更写入到持久的介质上。InnoDB的人在这一点上更聪明。他们意识到只有日志需要写入。脏数据页可以在发生溢出或Checkpoint的时候写入,因为它们的内容是冗余的。假如在崩溃的时候日志存活,使用日志中的信息来重建数据页是可行的。

所以InnoDB应该只写日志。或者确切地说,InnoDB应该写日志,直到它将所有正在提交的事务的日志写入完成。因为所有日志写作是串行,这意味着InnoDB也必须为其他事务写日志,但是这没关系。

这里我必须把它放到关键位置,因为这不是InnoDB必定会做的。如果在MySQL的配置文件my.cnf中将innodb_flush_log_at_trx_commit参数的开关设为0,那么InnoDB在提交时会避免写日志。这意味着,一个成功的提交不会保证所有的数据变更被持久化了,虽然这是ANSI/ISO标准要求的。只有在Checkpoint发生的时候才能保证持久化。

无论如何,你可以将innodb_flush_log_at_trx_commit设置为1,在这种情况下一切就没问题了,InnoDB会写日志,并且InnoDB会Flush到磁盘上。
我最好解释Flush是什么,嗯?通常仅仅是写入就足够了,但所有现代操作系统出于效率原因会缓存写操作。为了“保证”数据被写入,InnoDB必须坚持告诉操作系统“我的意思是真正的写,我想要磁盘上的写入磁头写入数据,不要返回给我直到这个物理操作完成。”这意味着在Windows系统上InnoDB会调用Windows api函数FlushFilBuffers,这个调用意思是“Flush缓存”。这里InnoDB与微软的:SQL Server 2000 在写入时一样使用“Write though”选项,而不是写后再刷新。

恢复

我们现在回到这一点,使记录麻烦的日志变得有价值的地方:如果发生崩溃,你可以恢复你的数据。

当崩溃发生时数据没有融合到磁盘,恢复是自动的。InnoDB读取最后一个Checkpoint日志记录,查看一下“脏页”是否在崩溃之前写入了,并且(如果他们不是)读取影响这些脏页的日志,并且应用到这些脏页上。这就是所谓的“前滚”,有两个原因使其变得很容易: (1)由于LSN的存在,所以InnoDB只需要比较LSN数字就可以让其同步,(2)因为我遗漏了一些细节。

很好。现在,崩溃的数据是否真的融合到了磁盘驱动器上了吗?那么恢复场景取决于你的准备工作。

场景一:日志了丢失了。好吧,你应该准备在一个单独的驱动器上保留一个日志拷贝。InnoDB没有这样明确的选项,但是有专门的操作系统方法。

场景二:数据库丢失并且日志被覆盖了。嗯,你应该预料到,当日志循环记录时,日志记录#5会将日志记录#1覆盖。还记得吗?因此如果你没有在写入日志记录#1之后后进行备份,你已经丢失了数据。

场景三:数据库丢失但日志是完好的。在这种情况下,祝贺你。你只需要恢复你最后的备份,并且让整个日志前滚。如果你自上次完全备份后又备份了几次(“归档日志”)会有抱怨信息,但我假定这个选项是不可操作的。顺便说一下,我不是讨论MySQL的binlog。虽然这是对恢复过程是必不可少的,但是这不是InnoDB的一部分,超出了讨论范围。

结论

当理解了InnoDB的日志,你知道有一些需要注意地方。排名不分先后

  • 使用较大的日志文件让上一次备份之后的日志不会被覆盖
  • 让日志文件和数据文存储在不同的磁盘上
  • 确保innodb_flush_log_at_trx_commit被正确的设置

希望本文能够帮助阐明严重缺乏文档的 MySQL InnoDB表的日志功能。如果你有任何问题或评论请用下面的论坛链接进讨论。

水平有限,如有问题,欢迎指正。