上节中提到数据库可能会发生“故障”,实际上数据库的故障类型有很多种,本节主要讨论这些故障的产生和处理方法,并建立数据库的存储管理模型,以便后面讨论事务的并发访问。
-
数据输入错误
数据内容的错误是无法避免的,如果用户在输入姓名或身份证号时输错了一位,那么错误是很难被直接发现的。处理数据输入错误的常见技术就是编写约束和触发器,可以及时找出不满足格式的错误数据。
-
介质故障
磁盘的部分区域损坏,一般只会影响少数几位数据,可以通过奇偶校验等方式检测到。如果整个磁盘损坏,要么为数据维护一个备份,要么采用 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 都重置为原值,要么将它们都更新为新值。