# 10.2 故障可恢复事务（Crash Recoverable Transaction）

为了能更好的理解Aurora的设计，在进一步介绍它是如何工作之前，我们必须要知道典型的数据库是如何设计的。因为Aurora使用的是与MySQL类似的机制实现，但是又以一种有趣的方式实现了加速，所以我们需要知道一个典型的数据库是如何设计实现的，这样我们才能知道Aurora是如何实现加速的。

所以这一部分是数据库教程，但是实际上主要关注的是，如何实现一个故障可恢复事务（Crash Recoverable Transaction）。所以这一部分我们主要看的是事务（Transaction）和故障可恢复（Crash Recovery）。数据库还涉及到很多其他的方面，但是对于Aurora来说，这两部分最重要。

首先，什么是事务？事务是指将多个操作打包成原子操作，并确保多个操作顺序执行。假设我们运行一个银行系统，我们想在不同的银行账户之间转账。你可以这样看待一个事务，首先需要定义想要原子打包的多个操作的开始；之后是操作的内容，现在我们想要从账户Y转10块钱到账户X，那么账户X需要增加10块，账户Y需要减少10块；最后表明事务结束。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcHT7txaWQGaDd9GWg%2F-MFcbjlQXZjFzWsE_Jgw%2Fimage.png?alt=media\&token=25e21b61-d939-4f66-b33a-5b8a0958bcfc)

我们希望数据库顺序执行这两个操作，并且不允许其他任何人看到执行的中间状态。同时，考虑到故障，如果在执行的任何时候出现故障，我们需要确保故障恢复之后，要么所有操作都已经执行完成，要么一个操作也没有执行。这是我们想要从事务中获得的效果。除此之外，数据库的用户期望数据库可以通知事务的状态，也就是事务是否真的完成并提交了。如果一个事务提交了，用户期望事务的效果是可以持久保存的，即使数据库故障重启了，数据也还能保存。

通常来说，事务是通过对涉及到的每一份数据加锁来实现。所以你可以认为，在整个事务的过程中，都对X，Y加了锁。并且只有当事务结束、提交并且持久化存储之后，锁才会被释放。所以，数据库实际上在事务的过程中，是通过对数据加锁来确保其他人不能访问。这一点很重要，理解了这一点，论文中有一些细节才变得有意义。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFdezpcTJM_vGJq_cod%2F-MFe8OWs4doa6KMuS0Cf%2Fimage.png?alt=media\&token=49e6504c-842d-41ca-8f99-478ee51ae350)

所以，这里具体是怎么实现的呢？对于一个简单的数据库模型，数据库运行在单个服务器上，并且使用本地硬盘。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFctl470rvs46WSDQ8D%2Fimage.png?alt=media\&token=111077d7-2208-4fa4-889c-4a5647c340a6)

在硬盘上存储了数据的记录，或许是以B-Tree方式构建的索引。所以有一些data page用来存放数据库的数据，其中一个存放了X的记录，另一个存放了Y的记录。每一个data page通常会存储大量的记录，而X和Y的记录是page中的一些bit位。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFcuURBLBYSr8iN79mC%2Fimage.png?alt=media\&token=e4226e34-41c5-4590-9982-0bf27047c49b)

在硬盘中，除了有数据之外，还有一个预写式日志（Write-Ahead Log，简称为WAL）。预写式日志对于系统的容错性至关重要。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFcvk6OfTX8YRmT9GSu%2Fimage.png?alt=media\&token=6eb771e5-410d-4948-8bc1-1bf4197effff)

在服务器内部，有数据库软件，通常数据库会对最近从磁盘读取的page有缓存。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFcwu5hk8tUHrV4qM_G%2Fimage.png?alt=media\&token=37a0255a-41e7-49a6-9cc6-3ff6d49e6e37)

当你在执行一个事务内的各个操作时，例如执行 X=X+10 的操作时，数据库会从硬盘中读取持有X的记录，给数据加10。但是在事务提交之前，数据的修改还只在本地的缓存中，并没有写入到硬盘。我们现在还不想向硬盘写入数据，因为这样可能会暴露一个不完整的事务。

为了让数据库在故障恢复之后，还能够提供同样的数据，在允许数据库软件修改硬盘中真实的data page之前，数据库软件需要先在WAL中添加Log条目来描述事务。所以在提交事务之前，数据库需要先在WAL中写入完整的Log条目，来描述所有有关数据库的修改，并且这些Log是写入磁盘的。

让我们假设，X的初始值是500，Y的初始值是750。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFcyJTKfSvephm5H0VZ%2Fimage.png?alt=media\&token=869b58c5-f5ea-4097-8c6a-9409a040d19c)

在提交并写入硬盘的data page之前，数据库通常需要写入至少3条Log记录：

1. 第一条表明，作为事务的一部分，我要修改X，它的旧数据是500，我要将它改成510。
2. 第二条表明，我要修改Y，它的旧数据是750，我要将它改成740。
3. 第三条记录是一个Commit日志，表明事务的结束。

通常来说，前两条Log记录会打上事务的ID作为标签，这样在故障恢复的时候，可以根据第三条commit日志找到对应的Log记录，进而知道哪些操作是已提交事务的，哪些是未完成事务的。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MFcqZr0XWMs1lgOCFcf%2F-MFd-5J0ZUsvpqi9xeMU%2Fimage.png?alt=media\&token=9fb751b6-48ce-4a94-aad6-4c64fc54862e)

> 学生提问：为什么在WAL的log中，需要带上旧的数据值？
>
> Robert教授：在这个简单的数据库中，在WAL中只记录新的数据就可以了。如果出现故障，只需要重新应用所有新的数据即可。但是大部分真实的数据库同时也会在WAL中存储旧的数值，这样对于一个非常长的事务，只要WAL保持更新，在事务结束之前，数据库可以提前将更新了的page写入硬盘，比如说将Y写入新的数据740。之后如果在事务提交之前故障了，恢复的软件可以发现，事务并没有完成，所以需要撤回之前的操作，这时，这些旧的数据，例如Y的750，需要被用来撤回之前写入到data page中的操作。对于Aurora来说，实际上也使用了undo/redo日志，用来撤回未完成事务的操作。

如果数据库成功的将事务对应的操作和commit日志写入到磁盘中，数据库可以回复给客户端说，事务已经提交了。而这时，客户端也可以确认事务是永久可见的。

接下来有两种情况。

如果数据库没有崩溃，那么在它的cache中，X，Y对应的数值分别是510和740。最终数据库会将cache中的数值写入到磁盘对应的位置。所以数据库写磁盘是一个lazy操作，它会对更新进行累积，每一次写磁盘可能包含了很多个更新操作。这种累积更新可以提升操作的速度。

如果数据库在将cache中的数值写入到磁盘之前就崩溃了，这样磁盘中的page仍然是旧的数值。当数据库重启时，恢复软件会扫描WAL日志，发现对应事务的Log，并发现事务的commit记录，那么恢复软件会将新的数值写入到磁盘中。这被称为redo，它会重新执行事务中的写操作。

这就是事务型数据库的工作原理的简单描述，同时这也是一个极度精简的MySQL数据库工作方式的介绍，MySQL基本以这种方式实现了故障可恢复事务。而Aurora就是基于这个开源软件MYSQL构建的。
