目录
05 事务处理、并发控制与故障恢复技术
5.1 事务处理
5.1.1 事务的定义
由某个用户所执行的一个不能被打断的对数据库的操作序列被称为‘事务’。
-
‘事务’是应用程序访问数据库的基本逻辑工作单位
-
‘事务’通常由一组对数据库的访问操作组成
-
一个‘事务’的执行过程是串行的,它将数据库从一个旧的一致性状态转换到一个新的一致性状态。;‘事务’执行过程中数据库中可能有数据不一致的现象,但事务执行结束时,数据具有一致性。
5.1.2 事务的性质(重要)
事务有四个性质:原子性、一致性、隔离性、持久性。(简称:ACID)
-
原子性:一个事务中,所有的数据库访问操作构成一个不可分割的操作序列,这些操作要么全部执行结束,要么一个都不要执行。
-
一致性:一个事务的成功执行总是将数据库从一个一致的状态转换到另一个一致的状态
-
数据库的‘状态’指数据库中所有数据对象的当前取值情况。
-
事务的‘一致性’基于这样一个假设:在一个事务开始执行之前数据库处于一个一致的状态,如果没有‘其它事务的干扰和系统故障’,那么当该事务执行结束时数据库仍然处于一致的状态。
-
-
隔离性:一个事务的执行与并发执行的其它事务间相互独立,互不干扰。
-
隔离性要求多个事务并发执行的最终结果,应该与它们的某种串行执行的最终结果相等,这被称为并发事务的可串行化。
-
-
持久性:一个事务一旦完成其全部操作后,它对数据库的所有更新应永久地反映在数据库中,即使以后系统发生故障也应该能够通过故障恢复来保留这个事务的执行结果。
-
事务的‘持久性’是由DBMS的恢复管理子系统实现的。
-
数据库管理系统通过其‘事务管理’子系统(含 ‘并发控制’子系统)、‘恢复管理’子系统、‘数据完整性保护’子系统来实现事务的原子性 (A)、一致性(C)、隔离性(I)和持久性(D)
-
5.1.3 事务活动
-
事务在开始执行后,立即进入”活动“状态。在”活动“状态中,事务执行对数据库的访问操作。访问操作具体来说就是读/写操作。
-
读操作:将数据从DBMS的系统缓冲区读入用户事务的私有工作区间。如果不在系统缓冲区则DBMS首先从磁盘读取数据
-
写操作:将修改后的数据”写入“数据库。这里一般是暂时存放在DBMS的系统缓冲区而不是直接写入磁盘
-
-
”预提交“状态:当事务的最后一条访问语句执行结束之后,事务进入‘预提交’状态。
在该阶段,必须确保将当前事务的所有修改操作的执行结果被真正写入到数据库的磁盘中去。在所有写磁盘操作执行完后,事务进入”提交“状态。
如果在预提交时执行失败(如发生系统故障),事务就转入”失败“状态。
-
”失败“状态:处于‘活动’状态的事务在顺利到达并执行完最后一条语句之前就中止执行,或者在‘预提交’状态下因发生系统故障而中止执行时,事务进入‘失败’状态。
-
事务从‘活动’状态转变为‘失败’状态的原因:
-
应用程序或用户主动放弃;
-
并发控制的原因而被放弃的事务,如发生死锁,封锁申请的超时等待;
-
发生系统故障。
-
-
从“预提交”转为“失败状态的原因:
-
发生系统故障。
-
-
-
“异常终止”状态:
处于‘失败’状态的事务,很可能已对磁盘中的数据进行了一部分修改。
为了保证事务的原子性,系统应该撤消(undo操作)该事务对数据库已作的修改。
-
回退(rollback):事务的‘回退’由DBMS的恢复子系统实现。
-
事务进入“异常终止”状态后,系统有两个选择:
-
作为一个新的事务,重新启动;
-
取消事务。
-
-
-
”提交“状态:
事务进入‘预提交’状态后,并发控制子系统将检查该事务与并发执行的其它事务之间是否发生干扰现象。
在检查通过以后,系统执行提交(commit)操作,把对数据库的修改全部写到磁盘上,并通知系统,事务已成功地结束,事务进入”提交“状态。
-
‘提交’状态和‘异常中止’状态都意味着一个事务的结束。
-
5.1.4 有关事务的语句
5.1.5 事务的组成
定义四种数据访问操作。之后的各种事务流程图中将频繁见到它们:
-
INPUT(A):将数据对象A的值从磁盘读入内存缓冲区。
-
OUTPUT(A):将内存缓冲区的数据对象A的值写入磁盘。
-
READ(A,t):将内存缓冲区中数据对象A的值读入内存变量t。
-
注意,有时候INPUT(A)操作不会显式写出,这时视为READ操作前有一个INPUT操作。
-
-
WRITE(A,t):将内存变量t的值写入内存缓冲区中数据对象A。
以A向B转账5000,A中原来有10000,B中原来有20000为例。数据库日志如下:
START T1; //事务开始语句
READ(A,t); //这里隐含一个INPUT操作。下同
t = t-5000;
WRITE(A,t);
READ(B,t);
t = t + 5000;
WRITE(B,t);
OUTPUT(A);
OUTPUT(B);
COMMIT T1; //写磁盘结束,提交当前事务
5.2 并发控制技术
5.2.1 事务的并发执行
并发操作可能带来以下三种数据不一致性:
-
丢失修改:两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了(覆盖了)T1提交的结果,导致T1的修改被丢失。
-
不可重复读:不可重复读是指事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。
-
脏读:读“脏”数据是指事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为“脏”数据,即不正确的数据。
避免不一致性的方法就是并发控制。
这章有几个关键名词:
-
调度:一个或多个事务中的数据库访问操作,按照这些操作被执行的时间排序所形成的一个操作序列。
-
串行调度:如果一个调度的操作组成方式如下:首先是一个事务的所有操作,然后是另一个事务的所有操作,依此类推,则我们称该调度是串行的,或称为‘串行调度’。
-
可串行化调度:除了串行调度之外也有其它的调度方式,比如使用分时方法,轮流执行一个事务的一个片段。如果一个非串行调度最终对数据库状态的影响和某个串行调度相同,则称该调度是可串行化的,或称为”可串行化调度“。
下图就是一个可串行化调度,它和先执行T1再执行事务T2效果相同:
-
事务及调度的表示方法
这里引入一套标记方法,用于后面讨论如何实现冲突可串行化。
事务用T1,T2,...表示。
事务T1读数据库对象x用r1(x)表示。
事务T1写数据库对象x用w1(x)表示。
-
冲突:是指调度中的一对连续操作(op1;op2),如果交换它们两者的执行顺序,那么两者之间至少有一个的行为会改变
-
对于给定的一个调度,如果通过一系列的非冲突操作的交换,能将该调度转化为一个串行调度,则该调度是一个可串行化调度。
-
冲突等价:如果通过一系列相邻操作的非冲突交换能够将一个调度转换为另一个调度,则我们称这两个调度是冲突等价的。
-
冲突可串行化:如果一个调度 S 冲突等价于一个串行调度,则我们称调度 S 是“冲突可串行化”的。
-
‘冲突可串行化’是‘可串行化’的一个充分条件,而非必要条件(TODO: ppt p71,这块没看懂)
-
5.2.2 封锁协议
常见的封锁类型有两种:
-
排它锁,又称为X锁。只有当数据对象A没有被其它事务封锁时,才能在A上施加X锁。
-
共享锁,又称为S锁。如果数据对象A没有被其它事务封锁,或者其它事务仅仅以‘S锁’的方式来封锁数据对象A时,事务T才能在数据对象A上施加‘S锁’。
接下来介绍三级封锁协议。不同级别的封锁协议可以防止不同的并发错误。
-
一级封锁协议:事务T在‘写’数据对象A之前,必须先申请并获得A上的‘X锁’,并维持到事务T的执行结束(包括Commit与Rollback)才释放被加在A上的‘X锁’。
-
二级封锁协议:满足一级封锁协议的基础上,事务T在’读’数据对象A之前,必须先申请并获得A上的’S锁’,在’读’操作完成后即可释放A上的’S锁’,但没有规定A释放S锁的时间。
-
三级封锁协议:满足一级封锁协议的基础上,事务T在‘读’数据对象A之前,必须先申请并获得A上的‘S锁’,并维持到事务T的执行结束(包括Commit与Rollback)才释放被加在A上的‘S锁’。
三级封锁协议与三种数据不一致现象的关系:
丢失修改 | 脏读 | 不可重复读 | |
---|---|---|---|
一级封锁协议 | 不会出现 | 会出现 | 会出现 |
二级封锁协议 | 不会出现 | 不会出现 | 会出现 |
三级封锁协议 | 不会出现 | 不会出现 | 不会出现 |
5.2.3 两阶段封锁协议
两阶段封锁协议的定义:一个事务的执行过程中,必须把锁的申请和释放分为两个阶段:
-
扩展阶段:事务可以申请其整个执行过程中所需要的锁
-
收缩阶段:事务开始释放获得的锁。事务一旦开始释放封锁,那么就不能再申请任何封锁。
如果一个事务中,所有的封锁请求都先于所有的解锁请求,则称该事务为‘两阶段封锁事务’,简称‘2PL事务’。
-
由2PL事务所构成的任意合法调度S都是冲突可串行化的。
5.2.4 封锁粒度
封锁粒度指一把锁封锁的数据对象的大小。封锁对象可以是:
-
逻辑数据单元:属性值、元组、关系、索引项、甚至整个数据库
-
物理数据单元:页、块
多粒度封锁:在一个系统中同时支持多种封锁粒度供事务选择,称为‘多粒度封锁’。
根据封锁力度的大小,可以画出一颗”多粒度树“,其中每个结点都是一个封锁对象:
意向锁:如果在多粒度封锁中还是只有S锁和X锁两种类型,则每次加解锁都需要在多粒度树中搜索所有的前驱节点和后继节点来进行锁的相容性检查,效率很低。所以提出了以下封锁类型:
-
意向共享锁(IS锁):如果对结点N加‘IS锁’,表示准备在结点N的某些后裔结点上加‘S锁’。
-
意向排它锁(IX锁):如果对结点N加‘IX锁’,表示准备在结点N的某些后裔结点上加‘X锁’。
-
共享意向排它锁(SIX锁):如果对结点N加‘SIX锁’,表示对结点N本身加‘S锁’,并准备在N的某些后裔结点上加‘X锁’。
带有意向锁的锁相容矩阵如下:
5.2.5 活锁与死锁
死锁:每个事务都可能拥有一部分‘锁’,并因申请其它事务所持有的‘锁’而等待,因此产生的循环现象称为死锁。(比如哲学家就餐问题)
死锁的处理方法:
-
预防法:采用特殊的锁申请方式。如顺序申请法、一次申请法
-
解除法:超时死锁检测法(事务的执行时间超时/锁申请的等待时间超时),等待图法,时间戳死锁检测法(牺牲较年轻的事务)
活锁:有部分事务因封锁申请得不到满足而处于长期等待状态,但其它的事务仍然可以继续运行下去,这种情况被称为“活锁”。(比如航班售票问题,一直有人来查询剩余票额导致买票线程申请不到X锁)
活锁的解决方法:先来先服务
5.3 数据库恢复技术
5.3.1 数据库故障分类
-
小型故障:事务内部故障
故障的影响范围在一个事务之内,不影响整个系统的正常运行
-
中型故障:系统故障、外部影响
可导致整个系统停止工作,但磁盘数据不受影响。在系统重启时,可通过当前的日志文件进行恢复
-
大型故障:磁盘故障、计算机病毒、黑客入侵
可导致内存及磁盘数据的严重破坏,需要对数据库做彻底的恢复
5.3.2 数据库故障恢复三大技术——1:数据转储
定期地将数据库中的内容复制到其它存储设备中的过程。又可分为静态转储和动态转储。
其中静态转储应该就是整个数据库为了转储停下一段时间服务(ppt上没有,应该时这个意思);动态转储时数据库可能还有事务在运行。
下面是一个动态转储数据库中A,B,C,D四个值的例子,转储时有两个事务在运行:
可以看到,转储得到的后备副本不能保证和数据库中数据的一致性。如果要使用该副本进行故障恢复,必须结合记载的数据库日志信息。
动态转储在日志中需要记载:
-
转储的开始点和结束点
-
转储过程中,事务的更新操作情况。
-
具体为一个四元组:<事务标识,更新对象,更新前值,更新后值>
-
-
转储过程中事务的提交情况。(事务的Commit/Abort)
5.3.3 数据库故障恢复三大技术——2:日志
日志的书写原则:先写日志,后修改磁盘。
日志分为三种:undo日志,redo日志,和undo/redo日志。
1. undo日志的规则:
2. redo日志的规则:
undo与redo日志都存在不足:Undo日志要求数据在事务结束后立即写到磁盘,可能增加需要执行磁盘I/O的次数;Redo日志要求事务提交和日志记录刷新之前将所有修改过的数据保留在内存缓冲区中,可能增加事务需要的平均缓冲区的数量。
3. undo/redo日志的规则:
-
格式:
<T,X,v,w>
其中v是更新前的值,w是更新后的值。
-
undo/redo日志的记载规则:
-
在由于某个事务T所做的改变而修改磁盘上的数据库元素X之前,更新记录<T,X,v,w>必须出现在磁盘上。
-
在每一条
<Commit T>
后面必须紧跟一条Flush Log 操作。
-
-
undo/redo日志的故障恢复过程:
-
根据
<Commit T>
是否出现在磁盘上决定事务T是否被提交。 -
按照从后往前的顺序,撤销所有未提交的事务。
-
按照从前往后的顺序,重做所有已提交的事务。
-
检查点:为了降低数据库故障恢复的开销,可以定期地在日志文件中插入检查点。
检查点又分为静止检查点和非静止检查点,区别为非静止检查点允许设置检查点过程中开启新的事务,静止检查点会暂停启动新事务。
在undo日志中插入静止检查点的步骤:
-
系统停止接受’启动新事务的请求‘
-
等待当前活跃的事务被提交或终止,且在日志中写入了<Commit T>或<Abort T>记录
-
写入日志记录
<CKPT>
,并将日志记录刷新到磁盘 -
重新开始接受新的事务。
在undo日志中插入非静止检查点的步骤:
-
写入日志记录
<Start CKPT(T1,...Tk)>
,并写日志到磁盘。其中T1,...,Tk是当前所有活跃事务的标记。 -
等待T1,...,Tk这些事务被提交或终止。期间允许开始执行其它新的事务。
-
当T1,...,Tk都已经完成时,写入日志记录
<End CKPT>
,并写日志到磁盘。
undo和redo日志的主要区别
-
恢复的目的不一样。undo日志用于被放弃事务(包括在发生故障时尚未结束的事务)的撤消工作;redo日志用于已提交事务的重做工作。
-
提交记录<Commit T>写入日志的时间不一样。
-
undo日志:在事务T的所有数据库磁盘修改操作(Output)结束后才能写入<Commit T>
-
redo日志:在写入提交记录<Commit T>后才能将事务T更新后的值写入磁盘
-
-
更新记录<T,X,V>中,保存的值V不一样。undo中保存的是旧值,redo中保存新值
TODO:将偷懒贴ppt的部分重新打一遍
5.3.3 数据库故障恢复三大技术——3:事务的撤销与重做
-
事务的撤销(undo):
-
反向扫描日志文件,找到应该撤销的事务
-
查找这些事务的更新操作,对更新操作做逆操作
-
如此反向扫描直到日志文件的头部。
-
-
事务的重做(redo):
-
正向扫描日志文件,查找应该重做的事务
-
查找这些事务的更新操作,对更新操作作重做处理
-
如此正向扫描直到日志文件的尾部。
-
ppt上有很多在undo日志、redo日志、undo&redo日志下进行事务撤销的练习题。
5.3.4 恢复策略
-
小型故障的恢复;利用未结束事务的undo操作进行恢复
-
中型故障的恢复:
-
对于非正常终止的事务执行undo操作
-
对于已完成提交的事务,可能其更新操作的修改结果还留在内存缓冲区中,尚未来得及写入磁盘,由于故障使内存缓冲区中的数据被丢失,所以执行redo操作
-
-
大型故障的恢复:
-
先利用后备副本进行数据库恢复,再利用日志进行数据库的恢复。
-
5.3.5 数据库镜像
将整个数据库中的数据(或主要数据)实时复制到另一个磁盘中。也称作“磁盘镜像”。
日志中的标记及含义汇总:
<Start T1>
:事务T1开始。
<Commit T1>
:提交事务T1。注意undo和redo两种日志类型写入这条的时间不同。
<T1,A,t>
:事务T1修改了A的值。undo日志中t
是A的旧值,redo日志中是A的新值。
<Abort T1>
:事务T1被终止。
<Start CKPT(T1,T2)>
:设置了一个非静止检查点,此时活跃的事务有T1和T2。
<End CKPT>
:结束检查点。