5.2 故障类型和事务模型
上节中提到数据库可能会发生“故障”,实际上数据库的故障类型有很多种,本节主要讨论这些故障的产生和处理方法,并建立数据库的存储管理模型,以便后面讨论事务的并发访问。
故障类型
数据输入错误
数据内容的错误是无法避免的,如果用户在输入姓名或身份证号时输错了一位,那么错误是很难被直接发现的。处理数据输入错误的常见技术就是编写约束和触发器,可以及时找出不满足格式的错误数据。
介质故障
磁盘的部分区域损坏,一般只会影响少数几位数据,可以通过奇偶校验等方式检测到。如果整个磁盘损坏,要么为数据维护一个备份,要么采用 RAID 阵列保存数据。此外还可以将数据的副本保存在多个远程节点上,一个节点的故障不会导致数据丢失,这就是分布式的思想。
系统故障
系统故障主要包括掉电和软件错误,由于内存是“易失性的”,如果数据提交了但没有写入到磁盘,当系统掉电时这部分数据就会丢失。软件错误可能会直接覆盖内存中的数据,就相当于数据已经丢失。解决这类问题的办法就是维护日志,将所有对数据库的修改操作都记录下来,以便重启系统后进行恢复。
机房故障
机房故障有可能是整个机房发生了火灾、爆炸等意外,或是自然灾害导致一片地区受到破坏,这种情况下 RAID 和数据校验都无法发挥作用,只有备份机制(异地)才可以防止数据丢失。
事务模型
为了更好地理解事务机制,这里建立一个如下图所示的模型。
事务管理器统筹管理事务的执行。
查询处理器负责解析 SQL 命令。
缓冲区管理器负责维护内存缓冲区和刷写数据。
日志管理器负责维护日志。
恢复管理器负责在系统重启后恢复数据。

事务原语
在数据库运行过程中,刚插入的数据往往不会直接写入磁盘,而是先缓存在内存中。对于一个运行中的数据库,可以将其地址空间简单分成三个部分:
持久化保存数据的磁盘空间。
缓冲区对应的内存或虚拟内存空间。
事务的局部地址空间(也在内存中)。
事务要读取数据,首先要将数据取到缓冲区中,然后缓冲区的数据可以被事务读取到局部空间。事务的写入过程与此相反,先在局部空间中创建新值,然后再将新数据拷贝到缓冲区中。缓冲区中的数据通常是由缓冲区管理器决定何时写入磁盘,而不是立刻持久化到磁盘。
为了便于研究日志和事务管理的细节,我们使用一系列原语来描述数据库操作:
INPUT(X):将数据库元素 X 从磁盘拷贝到缓冲区。
READ(X, t):将数据库元素 X 从缓冲区拷贝到事务的局部变量 t。
WRITE(X, t):将局部变量 t 的值拷贝到缓冲区的数据库元素 X,如果 X 不在缓冲区,先执行 INPUT(X)。
OUTPUT(X):将数据库元素 X 从缓冲区拷贝到磁盘。
在这里假设:数据库元素 X 的大小 = 磁盘块大小 = 缓冲区块大小。
【例 5.1】银行转账事务
一个数据库中有 A、B 两个账户(元素),A 和 B 之间进行转账操作,在任何一致的状态中它们的值的总和是固定的。
一个转账事务 T 主要有两个步骤:
A:A 账户减 10
B:B 账户加 10
假设 A 和 B 的初值都为 15,事务 T 从一个一致的状态(A+B=15+15=30)开始,事务正常执行且期间没有发生系统故障,那么最终的状态必然也是一致的,A 和 B 的值发生了变化,但他们的和没有发生变化(A+B=5+25=30)。
T 的执行包括从磁盘读取 A 和 B,执行运算,将 A 和 B 的新值写入缓冲区。之后缓冲区管理器会执行 OUTPUT 原语,将数据写回磁盘。表 5-1 中展示了事务 T 的执行过程,以及每个步骤执行之后 A 和 B 在缓冲区和磁盘中的值。
表 5-1
| 操作 | t | A(内存) | B(内存) | A(磁盘) | B(磁盘) |
|---|---|---|---|---|---|
| READ(A, t) | 15 | 15 | 15 | 15 | |
| t:= t-10 | 5 | 15 | 15 | 15 | |
| WRITE(A, t) | 5 | 5 | 15 | 15 | |
| READ(B, t) | 15 | 5 | 15 | 15 | 15 |
| t:= t+10 | 25 | 5 | 15 | 15 | 15 |
| WRITE(B, t) | 25 | 5 | 25 | 15 | 15 |
| OUTPUT(A) | 25 | 5 | 25 | 5 | 15 |
| OUTPUT(B) | 25 | 5 | 25 | 5 | 25 |
第 1 步 READ(A, t) 命令将 A 的值拷贝到局部变量 t 中,如果 A 不在缓冲区中,那么就会先执行 INPUT(A) 命令。
第 2 步将 t 减 10,这一步不会改变 A 在缓冲区和磁盘上的值。
第 3 步将 t 写到缓冲区的 A 中,这一步也不会影响磁盘上 A 的值。
直到第 7 步,OUTPUT(A) 将 A 的新值写入磁盘,完成持久化。
如果表 5-1 中的步骤顺利执行,那么事务前后数据库都处于一致性状态。
如果在 OUTPUT(A) 之前系统发生故障,因为磁盘上的数据没有任何变化,一致性得以保持。
如果系统故障在 OUTPUT(B) 之后发生,磁盘上的 A 和 B 都已经修改,仍然满足一致性要求。
如果系统故障发生在 OUTPUT(A) 和 OUTPUT(B) 之间,那么数据库就会处于不一致的状态,这种情况下就需要进行修复,要么将 A 和 B 都重置为原值,要么将它们都更新为新值。




