我们已经认识到,为了提高性能,数据库将数据存储在内存缓冲区中。但是,如果在事务提交时,服务器崩溃,你就会丢失所有内存中的数据,这将导致事务持久性问题。
你可以将所有数据写入磁盘,但是,如果服务器崩溃,你可能只写入一半数据,这会导致事务的原子性问题。
事务所做的任何修改写入必须要全部撤销或全部写入。
为解决这一问题,有两种解决方案:
WAL
当大型数据库需要很多事务时,影子拷贝/页会占用大量磁盘空间。这就是现代数据库使用 事务日志 的原因。事务日志必须存储在 稳定的存储 中。我不会深入介绍存储技术,只简单提一句,使用(至少)RAID 磁盘阵列能够有效防止磁盘失败。
很多数据库(包括 Oracle、 SQL Server 、 DB2 、 PostgreSQL 、MySQL 和 SQLite )使用 预写式日志(Write-Ahead Logging protocol,WAL) 。WAL 包括三个规则:
这个工作是由日志管理器完成的。简单来说,日志管理器就是,在缓存管理器和数据访问管理器(数据访问管理器将数据写入磁盘)之间,在数据写入磁盘之前,将每一个更新/删除/创建/提交/回滚写入事务日志。很简单,是不是?
不对! 经过这么多的学习,你应该知道,关于数据库的一切都可能引起“数据库效应”。严肃地说,日志管理器的问题是,找到一种既能写入日志,又能保持好性能的方式。如果写入事务日志太慢,它就会拖慢所有事情。
ARIES
1992 年,IBM 的研究者“发明了”一种改进版本的 WAL,称为 ARIES。ARIES 多多少少被大多数现代数据库使用。具体逻辑可能不一样,但是 ARIES 背后的概念已经被广泛使用。我给发明这个词加了引号,是因为根据 MIT 课程 ,IBM 研究者“什么都没干,只是写了篇事务恢复的最佳实践”。因为 ARIES 论文发表的时候,我只有 5 岁,我不关心那些尖酸刻薄的研究者们的流言蜚语。事实上,我之所以在这里写出来这个,主要是为了在我们开始最后一个技术部分之前稍微休息一下。我阅读了大量 关于 ARIES 的研究论文 ,发现它的确很有趣!在这部分我只介绍 ARIES 的大概,但是如果你想学习点真正的知识的话,我强烈推荐你去阅读这些论文。
ARIES 是 A lgorithms for R ecovery and I solation E xploiting S emantics(利用语义的恢复和隔离算法)的缩写。
这项技术的目的有两个:
数据库之所以要回滚事务,其原因是多方面的:
有时(比如网络失败)数据库能够从事务恢复。
这是怎么做到的?为回答这个问题,我们需要理解存储在日志记录中的信息。
日志
每一个 事务中的操作(增加/删除/修改)都会产生一条日志 。这个日志记录包括:
另外,磁盘上每一个页(保存数据的页,不是保存日志的页)都保存有最后修改数据的那个操作的日志记录 ID(LSN)。
*这种给出 LSN 的方式更复杂一些,因为它与保存日志的方式相关。但思路是一致的。
**ARIES 只使用逻辑 UNDO,因为物理 UNDO 会有很大的消耗。
注意:就我所知,只有 PostgreSQL 没有使用 UNDO,而是使用了一个垃圾回收守护进程来移除旧的数据。这与 PostgreSQL 使用的数据版本实现相关。
为了更好理解,下面是日志记录的简单的图形化例子。这个日志记录来自于查询 “UPDATE FROM PERSON SET AGE = 18;”。假设该查询在事务 18 中执行。
每个日志都有唯一的 LSN。同一事务的日志被链接到一起。日志是顺序相连的(链表中的最后一个日志对应着最后一个操作)。
日志缓冲
为避免写入日志带来的主要瓶颈,数据库会使用 日志缓冲 。
当查询执行器请求修改时:
事务提交,意味着事务中的每一个操作,上述 1,2,3,4,5 步已经完成。写入事务日志是很快的,因为这仅仅是“在事务日志某个地方加入一条记录”;但是,将数据写入磁盘就复杂得多,因为“需要按照能够快速读取的方式写入数据”。
STEAL 和 FORCE 策略
由于性能原因, 第 5 步可能会在事务提交之后完成 ,因为如果万一崩溃,还有机会按照 REDO 日志。这被称为 NO-FORCE 策略 。
数据库也可以选择 FORCE 策略(也就是说,第 5 步必须在提交之前完成)降低恢复期间的工作负载。
另一个问题是, 数据是一步一步写入磁盘的(STEAL 策略) 还是缓冲管理器需要等待提交命令,一次性写入( NO-STEAL 策略) 。STEAL 和 NO-STEAL 的选择取决于你想要什么:使用 UNDO 日志快速写入很长的恢复,还是快速恢复?
下面是恢复策略的影响总结:
恢复
好了,现在我们有了漂亮的日志,用用它们吧!
假设我们新来的临时工把数据库搞垮了(规则 No 1:总是临时工的错)。你重启数据库,开始恢复进程。
ARIES 从崩溃中恢复需要三个阶段:
在恢复期间,事务日志必须警惕恢复管理器执行的动作,以便保证写入磁盘的数据与事务日志中的是完全同步的。一个解决方案是,删除已经撤销完成事务日志记录,但这很困难。ARIES 则是在事务日志中写入补偿日志,删除那些逻辑删除的日志记录。
当事务“手动”取消或者由锁管理器取消(终止死锁)或仅仅是由于网络失败,分析阶段是不需要的。REDO 和 UNDO 所需信息保存在两个内存中的表:
这些表由缓存管理器和事务管理器为每一个新的事务事件进行更新。由于它们是在内存中的,数据库崩溃时就会被销毁。
分析阶段的工作是在崩溃之后使用事务日志重建这两个表*。为加速分析阶段,ARIES 提供了 检查点 标记,其思路是,将事务表和脏页表的内容不定时写入磁盘,并且写入此时最后一个 LSN,这样在分析阶段,只有在该 LSN 之后的日志才会被分析。
在写本文之前,我就知道这个话题是有多大,需要花费多少时间写一篇有深度的文章。事实证明我还是过于乐观,我花费了比预想多两倍的时间,但是我学到了很多。
如果你想很好学习数据库,我推荐阅读这篇研究论文《 Architecture of a Database System 》。这是一篇很好的介绍数据库的文章(110 页),适合非计算机专业人事阅读。这篇文章对编写本文计划给我很大帮助。它并没有像我的文章一样关注数据结构和算法,而是给出很多架构概念。
如果你仔细阅读本文,你应该知道数据库的强大之处。由于这是一篇很长的文章,我来提醒一下你阅读过什么:
但是数据库要聪明得多。例如,我没有碰触下面的问题:
所以,当你需要在充满 bug 的 NoSQL 数据库和像石头一样坚固的关系数据库之间选择时,我奉劝你要多多思考。不要将我的意思理解错了,某些 NoSQL 数据库非常棒,但是它们还年轻,只能处理某些应用的特定问题。
总结一下,如果以后有人问你,数据库是怎么工作的,我希望你不是像下面一样回答这个问题:
否则的话,你可以把这篇文章甩到他脸上。