📖 前言:事务是数据库操作的基本逻辑单元,事务处理技术主要包括数据库并发控制技术和恢复技术。本章首先介绍了事务的基本概念和四个特性,然后讨论事务并发操作可能引起数据库的不一致性,继而引入数据库的并发控制技术。在并发控制技术中,介绍了并发调度的可串行化概念和封锁协议,提及了封锁可能带来的死锁问题、死锁检测和解决办法,最后对数据库可能出现的故障种类,备份策略和恢复技术进行分析。


事务定义
事务和程序是两个概念
在SQL语言中,定义事务的语句有三条:
BEGIN TRANSACTION显式定义:
BEGIN TRANSACTION...(SQL语句)...COMMIT
or
BEGIN TRANSACTION...(SQL语句)...ROLLBACK # 回滚到事务开始的状态
其中值得一提的是,在Oracle中执行完语句不写COMMIT,退出后是不会保存的,就像你写完一个Word却没点保存,而SSMS不存在此情况。
隐式定义:
当用户没有显式地定义事务时,DBMS按缺省规定自动划分事务。如update也可以是一个事务。
COMMIT ROLLBACK 为了保证数据库的完整性(一致性),数据库管理系统必须维护事务的以下特性:ACID
问题的产生:
特点:在同一时刻并发运行的事务数可达数百个
不同的多事务执行方式:
事务并发执行带来的问题:
并发操作带来的数据不一致性
记号: R(x)表示读数据x;W(x)表示写数据x
例子:
(1)甲事务T1读取存款余额R=5000元;
(2)乙事务T2想在网上购物,读取存款余额5000元;
(3)甲事务T1由于需要取走1000元,则系统修改存款余额R=R-1000=5000-1000=4000,并将存款余额4000元写回数据库;
(4)乙事务网上购物转帐支取300元,则系统修改存款余额R= R-300=5000-300=4700,并将存款余额4700元写回数据库。
两个事务T1和T2读入同一数据并修改,T2的提交结果破坏了T1提交的结果,导致T1的更新被丢失。
T1T2① Read: R=5000② Read: R=5000③ R=R−1000Write: R=4000④ R=R−300Write: R=4700\begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ③ } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} \\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ④ } & \mathbf{R}=\mathbf{R}-300 \\ & \text {Write: } \mathbf{R}=\mathbf{4700} \\ \hline \end{array}T1 ① Read: R=5000 ② ③ R=R−1000 Write: R=4000 ④ T2Read: R=5000R=R−300Write: R=4700
例子:
(1)甲事务T1读取存款余额R=5000元;
(2)甲事务T1由于需要取走1000元,则系统修改存款余额R=R-1000=5000-1000=4000,并将存款余额4000元写回数据库,此时取款的事务还未提交;
(3)乙事务T2由于某种需要,读取存款余额为4000元;
(4)因为某种原因,甲事务的操作要撤销,此时对甲事务执行ROLLBACK操作,该帐户余额恢复为R=5000元。
读“脏”数据是指:
T1T2① Read: R=5000② R=R−1000Write: R=4000③ Read: R=4000④ ROLLBACKR恢复为5000 \begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} \\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ③ } & \text {Read: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ④ } \text {ROLLBACK} \\ \text { R恢复为5000 } \\ \hline \end{array}T1 ① Read: R=5000 ② R=R−1000 Write: R=4000 ③ ④ ROLLBACK R恢复为5000 T2Read: R=4000
例子:
(1)甲事务T1读取存款余额R=5000元;
(2)乙事务T2想在网上购物,读取存款余额5000元;
(3)乙事务网上购物转帐支取300元,则系统修改存款余额R= R-300=5000-300=4700,并将存款余额4700元写回数据库。
(4)甲事务T1再次读取帐户余额进行验证时发现前后两次读取值发生了变化,无法读取前一次读取的结果。
不可重复读包括三种情况:
① 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值
T1T2① Read: R=5000② Read: R=5000③ R=R−300Write: R=4700④ Read: R=4700\begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ③ } & \mathbf{R}=\mathbf{R}-\mathbf{3 0 0} \\ & \text { Write: } \mathbf{R}=\mathbf{4 7 0 0} \\ \text { ④ } \text {Read: } \mathbf{R}=\mathbf{4 7 0 0} \\ \hline \end{array}T1 ① Read: R=5000 ② ③ ④ Read: R=4700T2Read: R=5000R=R−300 Write: R=4700
② 当事务T1按照一定条件从数据库中读取某些纪录后,事务T2删除了其中的某些纪录,结果当事务T1再次按照同样条件读取该数据时,发现某些纪录已经不存在了;
③当事务T1按照一定条件从数据库中读取某些纪录后,事务T2插入了一些纪录,结果当事务T1再次按照同样条件读取该数据时,发现多出了某些数据。
分析:
可串行化(Serializable)调度
可串行性(Serializability)
例1:有两个事务T1和T2,分别包含如下操作:
T1:Read(A)T2:Read(B)T1:Read(A)~~~~~~~~~~~~T2:Read(B)T1:Read(A) T2:Read(B)
A:=A−2B:=B−2~~~~~~~~~A:=A-2~~~~~~~~~~~~~~~~~~B:=B-2 A:=A−2 B:=B−2
Write(A)Write(B)~~~~~~~~~Write(A)~~~~~~~~~~~~~~~~~~Write(B) Write(A) Write(B)
Read(B)~~~~~~~~~Read(B) Read(B)
B:=B+2~~~~~~~~~B:=B+2 B:=B+2
Write(B)~~~~~~~~~Write(B) Write(B)
现给出对这两个事务不同的调度策略
串行调度:
T1T2Read (A) A:=A-2 Write (A) Read (B) B:=B+2 Write(B) Read (B) B:=B-2 Write (B) \begin{array}{|l|l|} {T_{1}} & T_{2} \\ \hline \text { Read (A) } & \\ \text { A:=A-2 } & \\ \text { Write (A) } & \\ \text { Read (B) } & \\ \text { B:=B+2 } & \\ \text { Write(B) } \\ & \text { Read (B) } \\ & \text { B:=B-2 } \\ & \text { Write (B) } \end{array}T1 Read (A) A:=A-2 Write (A) Read (B) B:=B+2 Write(B) T2 Read (B) B:=B-2 Write (B)
T1T2Read (B) B:=B-2 Write (B) Read (A) A:=A-2 Write (A) Read (B) B:=B+2 Write(B) \begin{array}{|l|l|} {T_{1}} & T_{2} \\ \hline & \text { Read (B) } \\ & \text { B:=B-2 } \\ & \text { Write (B) } \\ \text { Read (A) } & \\ \text { A:=A-2 } & \\ \text { Write (A) } & \\ \text { Read (B) } & \\ \text { B:=B+2 } & \\ \text { Write(B) } \\ \end{array}T1 Read (A) A:=A-2 Write (A) Read (B) B:=B+2 Write(B) T2 Read (B) B:=B-2 Write (B)
可串行化调度:
T1T2Read (A) Read (B) A:=A-2 B:=B-2 Write(A)Write (B) Read (B) B:=B+2 Write(B) \begin{array}{|l|l|} {T_{1}} & T_{2} \\ \hline \text { Read (A) } & \\ & \text { Read (B) } \\ \text { A:=A-2 } & \\ & \text { B:=B-2 } \\ \text { Write(A)} & \\ & \text { Write (B) } \\ \text { Read (B) } & \\ \text { B:=B+2 } & \\ \text { Write(B) } \\ \end{array}T1 Read (A) A:=A-2 Write(A) Read (B) B:=B+2 Write(B) T2 Read (B) B:=B-2 Write (B)
不可串行化调度:
T1T2Read (A) A:=A-2 Read (B) Write(A)B:=B-2 Read (B) Write (B) B:=B+2 Write(B) \begin{array}{|l|l|} {T_{1}} & T_{2} \\ \hline \text { Read (A) } \\ \text { A:=A-2 } & \text { Read (B) } \\ \text { }\\ \text { Write(A)} & \\ & \text { B:=B-2 } \\ \text { Read (B) } & \\ & \text { Write (B) } \\ \text { B:=B+2 } & \\ \text { Write(B) } \\ \end{array}T1 Read (A) A:=A-2 Write(A) Read (B) B:=B+2 Write(B) T2 Read (B) B:=B-2 Write (B)
为了保证调度的可串行性,采用技术
商用的DBMS一般都采用封锁方法
排它锁(Exclusive Locks,简记为X锁)
共享锁(Share Locks,简记为S锁)
T2XS−T1XNNYSNYY−YYY\begin{array}{|l|l|l|l|} {~~~~~~}T_{2} & X & S & -\\ {T_{1}} & & &\\ \hline X & N & N & Y \\ \hline S & N & Y & Y \\ \hline - & Y & Y & Y \\ \end{array} T2T1XS−XNNYSNYY−YYY
Y=Yes,相容的请求
N=No,不相容的请求
在锁的相容矩阵中:
如何利用封锁机制解决不可串行化调度?
T1T2Xlock A Read (A) A:=A-2 Xlock B Read (B) Write(A)Unlock A B:=B-2 Xlock B 等待 Write (B) 等待 Unlock B Read (B) B:=B+2 Write(B) Unlock B \begin{array}{|l|l|} {T_{1}} & T_{2} \\ \hline \text { Xlock A }\\ \text { Read (A) } \\ \text { A:=A-2 } \\ & \text { Xlock B }\\ & \text { Read (B) } \\ \text { Write(A)} & \\ \text { Unlock A }\\ & \text { B:=B-2 } \\ \text { Xlock B }\\ \text { 等待 } & \text { Write (B) } \\ \text { 等待 } & \text { Unlock B }\\ \text { Read (B) } & \\ \text { B:=B+2 } & \\ \text { Write(B) } \\ \text { Unlock B }\\ \end{array}T1 Xlock A Read (A) A:=A-2 Write(A) Unlock A Xlock B 等待 等待 Read (B) B:=B+2 Write(B) Unlock B T2 Xlock B Read (B) B:=B-2 Write (B) Unlock B
前面提到的“丢失更新”问题:
T1T2① Read: R=5000② Read: R=5000③ R=R−1000Write: R=4000④ R=R−300Write: R=4700\begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ③ } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} \\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ④ } & \mathbf{R}=\mathbf{R}-300 \\ & \text {Write: } \mathbf{R}=\mathbf{4700} \\ \hline \end{array}T1 ① Read: R=5000 ② ③ R=R−1000 Write: R=4000 ④ T2Read: R=5000R=R−300Write: R=4700
解决方案:
T1T2① Xlock R Read: R=5000② Xlock R ③ R=R−1000等待 Write: R=4000等待 Unlock R 等待 ④ 获得Xlock R Read: R=4000R=R−300Write: R=3700Unlock R \begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Xlock R }\\ \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Xlock R }\\ \text { ③ } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} & \text{等待 }\\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} & \text{等待 }\\ \text { Unlock R } & \text{等待 }\\ \text { ④ } & \text{获得Xlock R } \\ & \text {Read: } \mathbf{R}=\mathbf{4 0 0 0} \\ & \mathbf{R}=\mathbf{R}-300 \\ & \text {Write: } \mathbf{R}=\mathbf{3700} \\ & \text {Unlock R } \\ \hline \end{array}T1 ① Xlock R Read: R=5000 ② ③ R=R−1000 Write: R=4000 Unlock R ④ T2Xlock R 等待 等待 等待 获得Xlock R Read: R=4000R=R−300Write: R=3700Unlock R
在一级封锁协议中,如果仅仅是读数据而不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读“脏”数据。
前面提到的“脏数据”问题:
T1T2① Read: R=5000② R=R−1000Write: R=4000③ Read: R=4000④ ROLLBACKR恢复为5000 \begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} \\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ③ } & \text {Read: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ④ } \text {ROLLBACK} \\ \text { R恢复为5000 } \\ \hline \end{array}T1 ① Read: R=5000 ② R=R−1000 Write: R=4000 ③ ④ ROLLBACK R恢复为5000 T2Read: R=4000
解决方案:
T1T2① Xlock R Read: R=5000② R=R−1000Write: R=4000③ Slock R ④ ROLLBACK等待 R恢复为5000 等待 Unlock R 等待 ⑤ 获得Slock R Read: R=5000Unlock R \begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Xlock R }\\ \text { Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } \mathbf{R}=\mathbf{R}-\mathbf{1 0 0 0} \\ \text { Write: } \mathbf{R}=\mathbf{4 0 0 0} \\ \text { ③ } & \text {Slock R }\\ \text { ④ } \text {ROLLBACK} & \text{等待 }\\ \text { R恢复为5000 } & \text{等待 }\\ \text { Unlock R }& \text{等待 } \\ \text { ⑤ } & \text {获得Slock R } \\ & \text { Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ & \text { Unlock R } \end{array}T1 ① Xlock R Read: R=5000 ② R=R−1000 Write: R=4000 ③ ④ ROLLBACK R恢复为5000 Unlock R ⑤ T2Slock R 等待 等待 等待 获得Slock R Read: R=5000 Unlock R
在二级封锁协议中,对数据进行读写操作前加X锁,防止了丢失修改的问题,对数据进行读操作前加S锁,防止了读“脏数据”的问题,但由于读完数据后即可释放S锁,所以它不能保证可重复读。
前面提到的“不可重复读”问题:
T1T2① Read: R=5000② Read: R=5000③ R=R−300Write: R=4700④ Read: R=4700\begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ③ } & \mathbf{R}=\mathbf{R}-\mathbf{3 0 0} \\ & \text {Write: } \mathbf{R}=\mathbf{4 7 0 0} \\ \text { ④ } \text {Read: } \mathbf{R}=\mathbf{4 7 0 0} \\ \hline \end{array}T1 ① Read: R=5000 ② ③ ④ Read: R=4700T2Read: R=5000R=R−300Write: R=4700
解决方案:
T1T2① Slock R Read: R=5000② Slock R ③ 等待 ④ Read: R=5000等待 Unlock R 获得Xlock R ⑤ Read: R=5000R=R−300Write: R=4700\begin{array}{|l|l|} \hline {\mathbf{T1}} & \mathbf{T2} \\ \hline \text { ① } \text {Slock R }\\ \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ \text { ② } & \text {Slock R }\\ \text { ③ } & \text {等待 }\\ \text { ④ } \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} & \text {等待 }\\ \text {Unlock R } & \text {获得Xlock R } \\ \text { ⑤ } & \text {Read: } \mathbf{R}=\mathbf{5 0 0 0} \\ & \mathbf{R}=\mathbf{R}-\mathbf{3 0 0} \\ & \text {Write: } \mathbf{R}=\mathbf{4 7 0 0} \\ \hline \end{array}T1 ① Slock R Read: R=5000 ② ③ ④ Read: R=5000Unlock R ⑤ T2Slock R 等待 等待 获得Xlock R Read: R=5000R=R−300Write: R=4700
X锁S锁一致性保证\hspace{5.2cm}{\color{blue}X~~锁} \hspace{4.9cm}{\color{green}S~~锁} \hspace{4.8cm}{\color{red}一~致~性~保~证}X 锁S 锁一 致 性 保 证
操作结束释放事务结束释放操作结束释放事务结束释放不丢失读改不读“脏”数据可重复性一级封锁协议✓✓二级封锁协议✓✓✓✓三级封锁协议✓✓✓✓✓\begin{array}{|c|c|c|c|c|c|c|c|} \hline & \begin{array}{c} {\color{blue}操作结束释放} \end{array} & \begin{array}{c} {\color{blue}事务结束释放} \end{array} & \begin{array}{c} {\color{green}操作结束释放} \end{array} & \begin{array}{c} {\color{green}事务结束释放} \end{array} & \begin{array}{c} {\color{red}不丢失读改} \end{array} & \begin{array}{c} {\color{red}不读“脏”数据} \end{array} & {\color{red}可重复性} \\ \hline \begin{array}{c} \text {一级封锁协议} \end{array} & & \checkmark & & & \checkmark & & \\ \hline \begin{array}{c} \text {二级封锁协议} \end{array} & & \checkmark & \checkmark & & \checkmark & \checkmark & \\ \hline \begin{array}{c} \text {三级封锁协议} \end{array} & & \checkmark & & \checkmark & \checkmark & \checkmark & \checkmark \\ \hline \end{array}一级封锁协议二级封锁协议三级封锁协议操作结束释放事务结束释放✓✓✓操作结束释放✓事务结束释放✓不丢失读改✓✓✓不读“脏”数据✓✓可重复性✓
两段锁协议是将所有事务的加锁和解锁分成两个阶段:
(1)获得封锁,也称为扩展阶段,在该阶段,事务可以获得任何数据项上的任何类型的锁;
(2)释放封锁,也称为收缩阶段,在此阶段,事务可以释放任何数据项上的任何类型的锁,但一旦释放一个锁之后,事务不能再获得任何其他加锁。
例
两个事务T1,T2:
T1:lock(A) lock(B) lock(C) unlock(B) unlock(C) unlock(A)
T2:lock(A) unlock(A) lock(B) lock(C) unlock(C) unlock(B)
T1遵守两段锁协议,T2不遵守两段锁协议。
遵守两段锁协议的可串行化调度
例2:有两个事务T1和T2,所包含的操作如表所示。
T1 T2 Slock B Slock A Read (B) Read (A) Unlock B Unlock A Xlock A Xlock B Read (A) Read (B) A: =A+B B: =A+B Write (A) Write (B) Ulock A Ulock B \begin{array}{|c|c|} \hline \text { T1 } & \text { T2 } \\ \hline \text { Slock B } & \text { Slock A } \\ \hline \text { Read (B) } & \text { Read (A) } \\ \hline \text { Unlock B } & \text { Unlock A } \\ \hline \text { Xlock A } & \text { Xlock B } \\ \hline \text { Read (A) } & \text { Read (B) } \\ \hline \text { A: =A+B } & \text { B: =A+B } \\ \hline \text { Write (A) } & \text { Write (B) } \\ \hline \text { Ulock A } & \text { Ulock B } \\ \hline \end{array} T1 Slock B Read (B) Unlock B Xlock A Read (A) A: =A+B Write (A) Ulock A T2 Slock A Read (A) Unlock A Xlock B Read (B) B: =A+B Write (B) Ulock B
假设有初值A=10,B=50,
按照T1→T2的串行结果是A=60,B=110,
按照T2→T1的方式串行的结果是A=70,B=60。
T1 T2 Slock B Read (B) XlockA SlockA 等待 等待 Ulock B 等待 Read (A) 等待 A: =A+B 等待 Write (A) 等待 UlockA Read (A) Xlock B Unlock A Read (B) B: =A+BWrite (B) UlockB \begin{array}{|l|l|} \hline \text { T1 } & \text { T2 } \\ \hline \text { Slock B } & \\ \hline \text { Read (B) } & \\ \hline \text { XlockA } & \text { SlockA } \\ \hline & \text { 等待 } \\ \hline & \text { 等待 } \\ \hline \text { Ulock B } & \text { 等待 } \\ \hline \text { Read (A) } & \text { 等待 } \\ \hline \text { A: =A+B } & \text { 等待 } \\ \hline \text { Write (A) } & \text { 等待 } \\ \hline \text { UlockA } & \text { Read (A) } \\ \hline & \text { Xlock B } \\ \hline & \text { Unlock A } \\ \hline & \text { Read (B) } \\ \hline & \text { B: }=A+B \\ \hline & \text { Write (B) } \\ \hline & \text { UlockB } \\ \hline \end{array} T1 Slock B Read (B) XlockA Ulock B Read (A) A: =A+B Write (A) UlockA T2 SlockA 等待 等待 等待 等待 等待 等待 Read (A) Xlock B Unlock A Read (B) B: =A+B Write (B) UlockB
两个事务T1和T2均遵守两段锁协议,其执行结果为A=60,B=110,跟按照T1→T2顺序的串行执行的结果相同,因此对这两个事务的并发调度是可串行化的,是正确的。
遵守两段锁协议的事务可能发生死锁
T1 T2 Slock B Read (B) Slock A Read (A) Xlock A 等待 Xlock B 等待 等待 \begin{array}{|l|l|} \hline \text { T1 } & \text { T2 } \\ \hline \text { Slock B } & \\ \hline \text { Read (B) } & \\ \hline & \text { Slock A } \\ \hline & \text { Read (A) } \\ \hline \text { Xlock A } & \\ \hline \text { 等待 } & \text { Xlock B } \\ \hline \text { 等待 } & \text { 等待 } \\ \hline \end{array} T1 Slock B Read (B) Xlock A 等待 等待 T2 Slock A Read (A) Xlock B 等待
和操作系统一样,封锁的方法可能引起活锁和死锁。
如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求……T2在不断重复尝试获取封锁数据R,可能需永远等待,这就是活锁的情形。
活锁指的是事务没有被阻塞,导致一直尝试、失败、尝试、失败……,避免活锁最简单的方法是先来先服务的策略。
事务T1封锁了数据R1
T2封锁了数据R2
T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁
接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁
这样T1在等待T2,而T2又在等待T1,T1和T2两个事务永远不能结束,形成死锁
T1 T2 lock R1 ⋅⋅lock R2 ⋅⋅lock R2 ⋅等待 lock R1 等待 等待 等待 等待 \begin{array}{|l|l|} \hline \text { T1 } & \text { T2 } \\ \hline \text { lock R1 } & ~~~~~~~ \cdot \\ \hline ~~~~~~~ \cdot & \text { lock R2 } \\ \hline ~~~~~~~ \cdot & ~~~~~~~ \cdot \\ \hline \text { lock R2 } & ~~~~~~~ \cdot \\ \hline \text { 等待 } & \text { lock R1 } \\ \hline \text { 等待 } & \text { 等待 } \\ \hline \text { 等待 } & \text { 等待 } \\ \hline \end{array} T1 lock R1 ⋅ ⋅ lock R2 等待 等待 等待 T2 ⋅ lock R2 ⋅ ⋅ lock R1 等待 等待
OS中预防死锁的方法
要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
结论:
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
用事务等待图动态反映所有事务的等待情况

并发控制子系统周期性地(比如每隔数秒)生成事务等待图,检测事务。如果发现图中存在回路,则表示系统中出现了死锁。
故障是不可避免的
数据库的恢复
以后,事务故障仅指这类非预期的故障
发生系统故障时,事务未提交
UNDO)所有未完成事务发生系统故障时,事务已提交,但缓冲区中的信息尚未完全写回到磁盘上。
REDO)所有已提交的事务称为硬故障,指外存故障
(1)重装转储的备份副本(.mdf)到新的磁盘,使数据库恢复到转储时的一致状态;
(2)在日志(.ldf)中找出转储后所有已提交的事务;
(3)对这些已提交的事务进行REDO处理,即子系统撤消所有未完成事务,对所有已提交的事务进行重做。
计算机病毒是一种人为的故障或者破坏,病毒程序不同于其他程序,它可以繁殖并传播,并造成对计算机系统包括数据库系统的危害。计算机病毒的种类很多,不同病毒有不同特征。有的计算机病毒传播很快,一旦侵入系统就马上摧毁系统;有的病毒有较长潜伏期,计算机感染数天或数月后才开始发病;有的只对特定的某些程序或数据感兴趣。
恢复操作的基本原理:冗余
恢复机制涉及的关键问题

转储方法:
(1)从转储的运行状态,可分为静态转储与动态转储
(2)从转储的进行方式,可分为海量转储与增量转储
静态转储
动态转储
海量转储:每次转储全部数据库
增量转储:只转储上次转储后更新过的数据
海量转储与增量转储比较:
注:上述转储状态和转储方式可以两两组合
日志文件定义:日志文件(log)是用来记录事务对数据库的更新操作的文件
日志文件的格式:
BEGIN TRANSACTION)COMMIT或ROLLBACK)具体来说,每个日志记录的格式为:
(【事务标识】(标明是哪个事务),【操作类型】(插入、删除或修改),【操作对象】(记录内部标识),【前像】(对插入操作而言,此项为空值),【后像】(对删除操作而言, 此项为空值))
举例说明日志文件记录,对于下面每次操作,在日志文件中写一个记录:
(1)事务T开始,日志记录为(T,start, , , )
(2)事务T修改对象A,日志记录为(T,update,A,前像,后像)
(3)事务T插入对象A,日志记录为(T,insert,A, ,后像)
(4)事务T删除对象A,日志记录为(T,delete,A,前像, )
(5)事务T提交,日志记录为(T,commit, , , )
(6)事务T回滚,日志记录为(T,rollback, , , )
每个日志记录的格式为:
(【事务标识】(标明是哪个事务),【被更新的数据块】)
日志文件的作用:
(1)当数据库发生的是事务故障和系统故障时,直接根据日志文件对相应的数据库操作进行UNDO和REDO操作即可;
(2)当发生介质故障时,

登记日志文件的基本原则:
(1)登记的次序必须严格按照并发事务执行的时间次序;
(2)必须先写日志文件,后写数据库,并且日志文件不能和数据库放在同一磁盘上,要经常把它复制到磁带上。
事务故障:事务在运行至正常结束提交前被终止
恢复方法:应该撤消(UNDO)该事务对数据库的一切更新
事务故障的恢复由系统自动完成,对用户是透明的,不需要用户干预
系统故障造成数据库不一致状态的原因
恢复方法:
Undo 故障发生时未完成的事务Redo 已完成的事务系统故障的恢复由系统在重新启动时自动完成,不需要用户干预
具体措施:
(1)检查磁盘的毁坏程度,必要时更换磁盘;
(2)然后修复系统(包括操作系统和DBMS),重新启动系统;
(3)重新装入最近的后备副本,使数据库恢复到最近一次转储时的一致性状态;
(4)重新装入有关的日志文件副本,对日志记录中转储点之后的已提交的事务进行REDO操作,将数据库恢复到故障前某一时刻的一致状态。
SQL Server 支持四种备份类型:
这里主要介绍前3种备份方式。
完全备份是将数据库中的全部信息进行备份,它是恢复的基线,可以与前面提到的海量转储联系起来。在进行完全备份时,不但备份数据库的数据文件、日志文件,还备份文件的存储位置信息以及数据库中的全部对象。完全备份需要消耗较多的时间和系统资源,一般可以几天或几周进行一次。恢复日志备份和差异备份时都依赖完全数据库备份。
例3:完全备份教学管理信息系统数据库
方法一:使用SQL语句
BACKUP DATABASE 教学管理信息系统
TO 教学管理信息系统_FULL
WITH INIT
方法二:使用对象资源管理器


差异备份是备份从最近的完全备份之后数据库的全部变化内容。它以完全备份为基准点,备份完全备份之后变化了的数据文件、日志文件和其他被修改的内容。差异备份比完全备份需要的时间短。
例4:差异备份教学管理信息系统数据库。
方法一:使用SQL语句
BACKUP DATABASE 教学管理信息系统
TO 教学管理信息系统_DIFFER
WITH DIFFERENTIAL
提示:教学管理信息系统_DIFFER是备份设备。WITH DIFFERENTIAL参数选项为差异备份。
方法二:使用对象资源管理器
事务日志备份是对上次备份(可以是完全备份、差异备份和日志备份)之后到当前备份时间所记录的日志内容进行备份。应该经常创建事务日志备份,减少丢失数据的危险。可以使用事务日志备份将数据库恢复到特定的即时点。
例5:事务日志备份教学管理信息系统数据库。
方法一:使用SQL语句
BACKUP LOG 教学管理信息系统
TO 教学管理信息系统_LOG
WITH INIT
提示:教学管理信息系统_LOG是备份设备。
方法二:使用对象资源管理器
注意:简单恢复模式下无法进行日志备份,更改恢复模式的方法:选定数据库—右键属性—选项----恢复模式更改

以上三种备份方式可以不同组合以符合各种应用需要,还需要适合的备份策略。
(1)完全备份策略
当对数据修改不是很频繁,而且允许一定量的数据丢失时,可以选择完全备份策略。
(2)完全备份加事务日志备份
如果用户不允许丢失太多的数据库,而且又不希望经常耗费过多时间和资源进行完全备份,这时可在两次完全备份中间进行多次事务日志备份,即完全备份加事务日志备份策略。
(3)完全备份加差异备份再加事务日志备份
进行一次完全备份所需时间比较长,如果用户希望将进行完全备份的时间间隔再加大一些,比如每周进行一次。我们可以采取第三种备份策略,即完全备份加差异备份和事务日志备份策略。
例6:从完全备份“教学管理信息系统_FULL”中恢复“教学管理信息系统_还原练习”数据库。
方法一:使用SQL语句
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_FULL
WITH RECOVERY
方法二:使用对象资源管理器
例7:从差异备份“教学管理信息系统_DIFFER”中恢复“教学管理信息系统”数据库。
分析:从差异备份中恢复,先要进行完全备份恢复,再从差异备份恢复,所以需要两步操作。
方法一:使用SQL语句
①先从完全备份恢复,使用NORECOVERY选项。
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_FULL
WITH NORECOVERY
②再从差异备份中恢复,使用RECOVERY选项。
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_FULL
WITH RECOVERY
提示:NORECOVERY指出在执行数据库恢复后不回滚未提交的事务。RECOVERY与NORECOVERY刚好相反,要求在执行数据库恢复后回滚未提交的事务。
方法二:使用对象资源管理器

例8:从日志备份“教学管理信息系统_LOG”中恢复“教学管理信息系统”数据库。
恢复日志备份的时候,SQL Server只恢复事务日志中所记录的数据更改。恢复步骤是首先恢复完全数据库备份;如果存在差异备份,则再恢复差异备份;最后恢复日志备份。
方法一:使用SQL语句
①先从完全备份恢复,使用NORECOVERY选项。
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_FULL
WITH NORECOVERY
②再从差异备份中恢复,使用NORECOVERY选项。
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_ DIFFER
WITH NORECOVERY
③最后使用日志备份恢复,使用RECOVERY选项。
RESTORE DATABASE 教学管理信息系统
FROM 教学管理信息系统_ LOG
WITH RECOVERY
方法二:使用对象资源管理器
快照复制是完全按照数据和数据库对象出现时的状态来复制和分发它们的过程。快照复制分发特定时刻的数据,并不需要连续地监视数据更新,因为已发布数据的变化不被增量地传播到订阅服务器,而是周期性的被一次复制。快照复制是复制那些经常改变的数据的最佳方法,同时也适合于那些不需要最新数据和低滞后时间的场合。在进行同步时,整个快照被创建并发往订阅服务器。
使用事务复制,初始快照数据将被传播到订阅服务器,因此该订阅服务器就具有了一个所谓的初始负载,这是可以开始工作的内容。当发布服务器上发生数据修改时,这些单独的事务会被及时捕获并复制到订阅服务器。并保留事务边界,当所有的改变都被传播后,所有订阅服务器将具有与传播服务器相同的值。
事务复制适用于以下场合:
(1)增量改变需要在发生时传播到订阅服务器。
(2)事务需要坚持其ACID(原子、一致性、隔离和持久性)属性。
(3)订阅服务器频繁地以可靠的方式连接到发布服务器上。
任意计算机上的更新将在稍后复制到另一台计算机上。合并复制是从发布服务器向订阅服务器分发数据的过程,允许发布服务器和订阅服务器在连接或非连接状态下进行更新,然后在站点连接状态下合并这些更新。
合并复制包括默认和定制冲突解决两种选择,用户可以在配置合并复制的同时定义它。在发生冲突时,合并代理程序将调用一个解决程序,该程序将判断哪些数据应被接受和传播到其他站点。合并复制适用于以下场合:
(1)多个订阅服务器需要在不同时间更新数据并将数据改变传播到发布服务器和其他订阅服务器。
(2)订阅服务器需要接收数据并脱机更改数据,然后与发布服务器及其他订阅服务器同步这些改变。
(3) 当数据在多个站点上进行更新时,可能会导致一些冲突。这主要因为数据首先被过滤到分区中,然后才发布到不同的订阅服务器那里,或者是因为应用程序使用方式的原因。但是,如果发生了冲突,ACID属性的冲突是可以接受的。
【单选题】DBMS的恢复子系统,保证了事务__________的实现。
A、原子性
B、一致性
C、隔离性
D、持久性
【单选题】事务是数据库执行的基本工作单位,如果一个事务执行成功,则全部更新提交;如果一个事务执行失败,则已做过的更新被恢复原状,好像整个事务从未有过这些更新,这就保持数据库处于( )状态。
A、安全性
B、一致性
C、完整性
D、可靠性
【填空题】如果数据库中只包含成功事务提交的结果,就说数据库处于__________状态。
【单选题】在第一个事务以S封锁方式读数据A时,第二个事务对数据A的读方式会遭到失败的是( )。
A、实现X封锁的读
B、实现S封锁的读
C、不加封锁的读
D、实现共享型封锁的读
【多选题】设T1和T2两个事务,它们对数据A的并发操作如下图所示(其中SLOCK A表示对数据A上S锁,UNLOCK A表示对数据A解锁,COMMIT表示提交操作)。对这个并发操作,下面5个评价中()和()两条评价是正确的。
T1T2① 请求 SLOCK A 读 A=18②请求 SLOCK A 读 A=18③ A=A+10写回 A=28COMMIT UNLOCK A 写回 A=18④ COMMIT UNLOCK A\begin{array}{|l|l|} \hline T_{1} & T_{2} \\ \hline \text { ① 请求 } & \\ \text { SLOCK A } & \\ \text { 读 } \mathrm{A}=18 & \\ \text { ②} & \text { 请求 } \\ & \text { SLOCK A } \\ & \text { 读 } \mathrm{A}=18 \\ \text { ③ } \mathrm{A}=\mathrm{A}+10 & \\ \text { 写回 } \mathrm{A}=28 & \\ \text { COMMIT } & \\ \text { UNLOCK A } & \text { 写回 } \mathrm{A}=18 \\ \text { ④ } & \text { COMMIT } \\ & \text { UNLOCK } \mathrm{A} \\ \hline \end{array}T1 ① 请求 SLOCK A 读 A=18 ② ③ A=A+10 写回 A=28 COMMIT UNLOCK A ④ T2 请求 SLOCK A 读 A=18 写回 A=18 COMMIT UNLOCK A
A、该操作不能重复读
B、该操作丢失修改
C、该操作符合完整性要求
D、该操作的第(1)步中,事务T1应申请X锁
E、该操作的第(2)步中,事务T2不可能得到对A的锁
【填空题】如果多个事务依次执行,则称事务是( )执行;如果利用分时的方法,同时处理多个事务,则称事务是( )执行。
【判断题】采用封锁技术时与封锁协议无关。
【判断题】在第一个事务以S锁方式读数据R时,第二个事务可以进行对数据R加S锁并写数据的操作。
【单选题】数据库恢复的基础是利用转储的冗余数据。这些转储的冗余数据包括_____。
A、数据字典、应用程序、审计档案、数据库后备副本
B、数据字典、应用程序、日志文件、审计档案
C、日志文件、数据库后备副本
D、数据字典、应用程序、数据库后备副本
【单选题】关于具有检查点的恢复技术,下列说法不正确的是()。
A、检查点最大限度地减少数据库完全恢复时所必须执行的日志部分
B、使用检查点方法可以改善恢复效率
C、无论事务在检查点之前还是之后提交,都需要执行REDO操作
D、恢复子系统可以定期地建立检查点,也可以按照某种规则建立检查点
【单选题】用于数据库恢复的重要文件是( )
A、数据库文件
B、索引文件
C、日志文件
D、备注文件
【单选题】后援副本的用途是( )。
A、安全性保障
B、一致性控制
C、故障后的恢复
D、数据的转储
【单选题】“所有事务都是两段式”与“事务的并发调度是可串行化”两者之间的关系是( )。
A、同时成立与不成立
B、没有必然的联系
C、前者蕴涵后者
D、后者蕴涵前者
【单选题】写一个修改到数据库中,与写一个表示这个修改的运行记录到日志文件中是两个不同的操作,对这两个操作的顺序安排应该是( )。
A、前者先做
B、后者先做
C、由程序员在程序中做安排
D、哪一个先做由系统决定
答案:1.D 2.B 3.一致性 4.A 5.BD 6.串行、并行 7.× 8.× 9.C 10.C 11.C 12.C 13.C 14.B
OK,以上就是本期知识点“事务并发控制和恢复技术”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~