数据库概论.陈立军.06.事务
事务
事务概念
- transaction(交易)
- 交易的核心:完整性
- Jim Gray:在事务处理方面做出巨大的贡献
- 书籍:transaction processing: concept and techniques
- commitment
- 事务包含了很多操作,commitment 指把所有的操作完整的提交到数据库上
- It's like bacon and eggs. The chicken participants. The pig commited.
- 一致性条件
- 银行转账,两个账户的总和在转账前后保持不变
- 存在两个操作,转出账户余额减去转账金额,转入账户余额加上转账金额
- 必须保证这组操作的整体性
事务定义
- 事务是由一系列操作序列构成的执行单元,这些操作要么都做,要么都不做,是一个不可分割的工作单位
- \({\color{red}\mathrm{All\;or\;None}}\)
事务中的数据访问原语
- read(X):从数据库传送数据项 X 到事务的工作区中
- 从数据缓冲区读出来
- write(X):从事务的工作区中将数据项 X 写回数据库
转账事务的原语表达
- 事务 T 从 A 帐户过户 50 到 B 帐户
\[ \begin{aligned} &read(A);\\ &A := A –50;\\ &write(A);\\ &read(B);\\ &B := B + 50;\\ &write(B);\\ \end{aligned} \]
- 电子商务:事务是现代信息系统的基石
SQL中事务的定义
- 事务以 Begin transaction 开始,以 Commit transaction 或 Rollback
transaction 结束
- Commit transaction 表示提交,事务正常结束
- Rollback transaction 表示事务非正常结束,撤消事务已做的操作,回滚到事务开始时状态
- 具体的结束方式,通过加入某些条件判断
- terminate 与 abort
- Terminate:终止(中性)
- Abort:中止(失败,需要 rollback)
事务执行模式
显式事务
- 以 begin transaction 开始,以 commit 或 rollback 结束
隐含事务
- 事务自动开始,直到遇到 commit 或 rollback 时结束
- set implicit_transactions{ ON | OFF }
自动事务
- 每个数据操作语句作为一个事务
- update SC set GRADE = GRADE+15
事务中的错误检查
- GRADE 约束小于等于100,现在数据库中有两个 GRADE (80, 90)
- 事务
update SC set GRADE = GRADE+15
操作的结果- (80,90),事务出错,整个回滚
- 如下事务操作的结果
- (85, 95)
- 不做设置的前提下,会将第一个操作提交
- 设置
set XACT_ABORT ON
之后结果为 (80,90)
- (85, 95)
1 | Begin tran |
- 如下事务操作的结果
- (80, 90)
- rollback 把整个事务回滚
1 | Begin tran |
事务基本特性ACID
- \(\mathrm{ACID:Atomicity\;Consistency\;Isolation\;Durability}\)
原子性(Atomicity)
- 事务中包含的所有操作要么全做,要么全不做
- 原子性由恢复机制实现
- 通过日志进行恢复
一致性(Consistency)
- 事务的隔离执行必须保证数据库的一致性
- 事务开始前,数据库处于一致性的状态;事务结束后,数据库必须仍处于一致性状态
- 数据库的一致性状态由用户来负责
- 如银行转帐,转帐前后两个帐户金额之和应保持不变
- 意大利香肠术,Salami technique
- 偷小钱的盗窃术被称为意大利香肠术(意大利香肠切得很薄,偷一片看不出来)
隔离性(Isolation)
- 系统必须保证事务不受其它并发执行事务的影响
- 对任何一对事务 T1,T2,在 T1 看来,T2 要么在 T1
开始之前已经结束,要么在 T1 完成之后再开始执行
- 等价于串行执行的效果
- 隔离性通过并发控制机制实现
持久性(Durability)
- 一个事务一旦提交之后,它对数据库的影响必须是永久的
- 系统发生故障不能改变事务的持久性
- 系统的抗故障能力
- 大企业的数据,备份,多个物理实体(物理上相隔较远,避免自然灾害的影响)
- 持久性通过恢复机制实现
- 持久性:将来总能够再现这个事务
事务生命周期图
- 部分提交状态:事务的所有操作都做完了
- 一个事务的所有操作都做完了,并不意味着一定能成功提交,因为要保证持久性
- 数据库在做完事务的所有操作之后,在向程序客户端发送成功消息之前,把这个事务对应的日志记录从内存写入磁盘
- 如果系统崩溃,内存中的数据丢失了,可以从日志记录恢复数据
事务调度
- 事务的执行顺序称为一个调度,表示事务的指令在系统中执行的时间顺序
- 一组事务的调度必须保证
- 包含了所有事务的操作指令
- 一个事务中指令的顺序必须保持不变
事务调度例子
- \(T_1:i_{11},i_{12},T_2:i_{21},i_{22}\)
- \(S_1:i_{11},i_{12},i_{21}\)
- \(S_2:i_{11},i_{22},i_{12},i_{21}\)
- \(S_2:i_{11},i_{21},i_{12},i_{22}\)
- \(S_1,S_2\) 不是一个调度,\(S_3\) 是一个调度
并行与串行
串行调度
- 在串行调度中,属于同一事务的指令紧挨在一起
- 串行调度总是正确的
- 对于有 \(n\)个事务的事务组,可以有 \(n!\) 个有效调度
并行调度
- 在并行调度中,来自不同事务的指令可以交叉执行
- 当并行调度等价于某个串行调度时,则称它是正确的
- \(n\) 个事务,\(t_i\) 有 \(k_i\) 条指令,则可能的并发调度有多少个
- \(\dfrac{(\sum k_i)!}{\prod(k_i!)}\)
- 事务内有序
并行和串行的比较
基本比较
- 并行事务有可能破坏数据库的一致性
- 串行事务效率低
并行的优点
- 一个事务由不同的步骤组成,所涉及的系统资源也不同。这些步骤可以并发执行,提高系统的吞吐量
- 系统中存在着周期不等的各种事务,串行会导致难于预测的时延。如果各个事务所涉及的是数据库的不同部分,采用并发会减少平均响应时间
事务调度例子
- 两个转账事务
- \(T_1\):从 A 过户 50 到 B
- \(T_2\):从 A 过户存款的 10% 到 B
\[ \begin{aligned} &T1\\ &read(A);\\ &A := A −50;\\ &write(A);\\ &read(B);\\ &B := B + 50;\\ &write(B);\\\\ &T2\\ &read(A);\\ &temp := A*0.1;\\ &A := A −temp;\\ &write(A);\\ &read(B);\\ &B := B + temp;\\ &write(B);\\ \end{aligned} \]
- 开始状态:A=1000,B=2000,A+B=3000
串行调度1
- \(T_1,T_2\)
- 结束状态:A=855,B=2145,A+B=3000
串行调度2
- \(T_2,T_1\)
- 结束状态:A=850,B=2150,A+B=3000
- 串行调度可能会有不同的结果,但是都满足一致性,都是正确的
并行调度1
\[ \begin{aligned} &T1:\;read(A);\\ &T1:\;A := A −50;\\ &T1:\;write(A);\\ &T2:\;read(A);\\ &T2:\;temp := A*0.1;\\ &T2:\;A := A −temp;\\ &T2:\;write(A);\\ &T1:\;read(B);\\ &T1:\;B := B + 50;\\ &T1:\;write(B);\\ &T2:\;read(B);\\ &T2:\;B := B + temp;\\ &T2:\;write(B);\\ \end{aligned} \]
- 需要保证操作 4 在操作 3 之后,否则是错误的
- 结束状态:A=855,B=2145,A+B=3000
- 等价于串行调度 \(T_1,T_2\)
并行调度2
\[ \begin{aligned} &T1:\;read(A);\\ &T1:\;A := A −50;\\ &T2:\;read(A);\\ &T1:\;write(A);\\ &T2:\;temp := A*0.1;\\ &T2:\;A := A −temp;\\ &T2:\;write(A);\\ &T1:\;read(B);\\ &T1:\;B := B + 50;\\ &T1:\;write(B);\\ &T2:\;read(B);\\ &T2:\;B := B + temp;\\ &T2:\;write(B);\\ \end{aligned} \]
- 错误的
- 结束状态:A=900,B=2150,A+B=3050
可恢复调度
- 事务的恢复
- 一个事务失败了,应该能够撤消该事务对数据库的影响
- 如果有其它事务读取了失败事务写入的数据,则该事务应该撤消
- 一个不可恢复调度的例子
T1 | T2 |
---|---|
read(A) | |
write(A) | |
read(A) | |
commit | |
read(B) | |
rollback |
- 回滚 T1 的时候,T2 已经提交了,如果 T2
被撤销了,那么就违反了事务的持久性
- 在真实场景中, 会向 T2 发起一个补偿事务
可恢复调度定义
- 对于每对事务 T1 与 T2,如果 T2 读取了 T1 所写的数据,则 T1 必须先于 T2 提交
无级联调度
- 级联调度
- 由于一个事务故障而导致一系列事务回滚
- 级联调度例子
T1 | T2 | T3 |
---|---|---|
read(A) | ||
read(B) | ||
write(B) | ||
read(A) | ||
write(A) | ||
read(A) | ||
rollback |
无级联调度定义
- 对于每对事务 T1 与 T2,如果 T2 读取了 T1 所写的数据,则 T1 必须在 T2 读取之前提交
- 无级联调度必是可恢复调度
- 要求比可恢复调度更加严格
并发调度中的不一致现象
丢失修改
- 两个事务 T1 和 T2 读入同一数据并修改,T1 提交的结果破坏了 T2 提交的结果,导致 T2 的修改丢失
- 一个例子:售票窗口
- 下面的操作只反映了售票窗口 T1 的结果,但是没有反应售票窗口 T2 的结果
- 写 —— 写不一致
读脏数据
- 事务 T1 修改某一数据,并将其写回磁盘,事务 T2 读取同一数据后,T1 由于某种原因被撤消,这时 T1 已修改过的数据恢复原值,T2 读到的数据与数据库中数据不一致,则 T2 读到的数据就是脏数据
- 一个例子
- 写 —— 读不一致
不能重复读
- 事务 T2 读取某一数据后,事务 T1 对其做了修改,当 T2 再次读取该数据时,得到与前次不同的值
- 读 —— 写不一致
- 发生概率比读脏数据小
- 例子
- r1(list) r2(list) w2(list) r2(count) w2(count) commit(t2) r1(count)
- r1(list) 和 r1(count) 不一致
发生幻象(Phantom)
- 事务 T2 按一定条件读取了某些数据后,事务 T1 插入了一些满足这些条件的数据,当 T2 再次按相同条件读取数据时,发现多了一些记录
- 不可重复读是指对同一条记录出现不一致情况,幻象指的是多出来或者少掉某些记录
- 防止幻象更难
- 例如
- 防范不可重复读只需要将原来在数据库中的数据锁住
- 防范幻象则比较难,怎么处理新来的数据