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

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

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

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

![](/files/-MFcbjlQXZjFzWsE_Jgw)

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

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

![](/files/-MFe8OWs4doa6KMuS0Cf)

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

![](/files/-MFctl470rvs46WSDQ8D)

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

![](/files/-MFcuURBLBYSr8iN79mC)

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

![](/files/-MFcvk6OfTX8YRmT9GSu)

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

![](/files/-MFcwu5hk8tUHrV4qM_G)

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

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

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

![](/files/-MFcyJTKfSvephm5H0VZ)

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

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

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

![](/files/-MFd-5J0ZUsvpqi9xeMU)

> 学生提问：为什么在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构建的。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-10-cloud-replicated-db-aurora/10.2-gu-zhang-ke-hui-fu-shi-wu-crash-recoverable-transaction.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
