『database-8』transaction
事务处理技术
一、事务概念
事务的特性:ACID
- 原子性:事务中的所有操作要么都做,要么都不做
- 一致性:事务执行的结果必须是从一个一致性状态到另一个一致性状态
- 隔离性:一个事务的执行不能被其它事务干扰,并发执行的各事务间不能相互干扰
- 持久性:一个事务一旦提交,对数据库的影响必须是永久的
注意:原子性 和 持久性 由恢复机制实现,隔离性由并发控制实现,一致性由事务的原子性保证
二、数据库恢复技术
数据库恢复:把数据库从错误状态恢复到某个正常状态的功能,保证了事务原子性,确保故障时可以恢复到正确状态
事务内部故障:分为可预期和不可预期故障(应用程序无法处理),意味着既没有提交、也没有显式回滚
系统故障:造成系统停止运转,使所有事务异常终止,但不会破坏数据库
介质故障:外存故障,如磁盘损坏,这会破坏数据库
计算机病毒:人为破坏
数据转储:DBA定期将整个数据库复制到另一个磁盘上保存起来
静态转储:无事务运行时转储,转储时不允许对数据库做任何读取或修改;保证副本一致性,但可用性低
动态转储:转储时允许对数据库做存取或修改;不影响可用性,但要把转储期对数据库的修改记录在日志中
海量转储:一次性转储全部数据库
增量转储:每次只转储上次转储后更新过的内容
日志文件:一条日志记录包括“事务标识”、“操作类型”、“操作对象”、“更新前旧值”、“更新后新值” 以数据块为单位的日志文件,日志记录包括 事务标识 + 被更新的数据块
- 动态转储:后备副本 + 日志文件 才能恢复数据库
- 静态转储:通过 日志文件 恢复转储结束 \(\Rightarrow\) 故障点间 的事务
注意:必须按照并发事务的执行先后顺序写入日志文件,且要先写日志、后写数据库
事务故障恢复:撤销操作,即反向扫描日志文件,对各更新操作执行逆操作,直至读到事务开始标志
系统故障恢复:撤销未完成的事务 + 重做已完成的事务,正向扫描日志文件:
- 将故障前已提交的事务加入到重做队列
- 将故障时未完成的事务加入到撤销队列
介质故障恢复:
- 装入最新的数据库后备副本,使数据库恢复到最近一次转储时的一致性状态
- 装入转储后的日志文件副本,重做已经完成了的事务
检查点技术:在日志文件中新增一类记录(检查点记录),新增重新开始文件
- 检查点记录:建立该记录时刻所有正在执行的事务清单 + 这些事务最近一个日志记录的地址
- 重新开始文件:记录各检查点记录在日志文件中的地址
动态维护日志文件:建立检查点 + 保存数据库状态
- 将日志缓存中的所有日志记录写回日志文件
- 在日志文件上写入一个检查点记录(先写日志)
- 将当前数据缓存中的所有数据记录写回磁盘数据库(后写数据库)
- 把检查点记录在日志文件中的地址记录在重新开始文件
检查点技术的恢复
- 根据重新开始文件的最后一个地址,找到日志中最后一个检查点记录
- 把最后一个检查点建立时刻所有正在运行的事务暂时加入到 UNDO-LIST
- 从检查点正向扫描日志文件,若该任务新开始,则将其暂时加到 UNDO;若该任务已提交,则将其从 UNDO 移动到 REDO
- 对 UNDO 中的任务执行撤销操作,对 REDO 中的任务执行重做操作
数据库镜像:每当主数据库更新时,DBMS自动把整个数据库复制到另一个磁盘上
- 数据库恢复:数据库故障时,镜像磁盘可供继续使用、以及数据恢复(不必重装数据库副本)
- 数据库可用:正常情况下,一个用户对数据库添加排他锁时,其他用户可读取镜像数据库上的数据
三、数据库并发控制
并发操作导致的数据不一致性:
- 丢失更新:两个事务读取同一数据并先后修改提交,T2的提交破坏了T1的提交结果
- 读脏数据:T2读取了T1修改的数据,但T1的修改数据被撤销,导致T2读到的数据不一致
- 不能重复读:T1读取数据后,T2更新数据,T1再次读数据时发现读取结果无法复现
封锁:排他锁(X)和 共享锁(S)
- 一级封锁协议:事务在修改数据前必须加 X
锁,直至事务结束后才释放
保证不丢失修改(不会被干扰覆盖),但由于读数据不需要申请锁,故不保证避免脏读 + 可重复读 - 二级封锁协议:在一级封锁协议的基础上,读数据前先加
S 锁,读完即释放
进一步保证不读“脏数据”(修改事务结束前不能读),但不保证可重复读(应该在事务中的所有读操作结束后才释放) - 三级封锁协议:在一级封锁协议的基础上,读数据前先加
S 锁,直至事务结束才释放
进一步保证可重复读
- 一级封锁协议:事务在修改数据前必须加 X
锁,直至事务结束后才释放
活锁:很长时间等待其它事务释放锁,轮不到自己
- 预防方法:先来先服务
死锁:互相持有锁,无法结束,解决方案如下:
- 预防死锁:
- 一次封锁法:一次性把事务所有资源全部加锁,否则不能执行,并发度较低
- 顺序封锁法:预先对各数据对象规定一个封锁顺序,实现难度较大
- 检测死锁:
- 超时法:等待时间超过规定期限,就认为发生死锁
- 等待图法:若等待图中存在有向回路,说明出现死锁
- 预防死锁:
封锁粒度:封锁粒度大,并发度低,封锁开销小;封锁粒度小,并发度高。封锁开销大
- 多粒度树:根表示整个数据库,自顶向下是数据库、关系、元组
- 显示封锁:直接加到对象上的锁
- 隐式封锁:该对象上没有独立加锁,但其上级节点加锁导致该对象加了同类型的锁
- 封锁检查:对某个对象加锁,需要同时做以下检查
- 是否与其本身上的显示封锁冲突
- 是否与其隐式封锁冲突(检查其所有上级节点)
- 是否与其下级节点显示封锁冲突(检查其所有下级节点)
- 意向锁:把那个对称表记住
- 多粒度树:根表示整个数据库,自顶向下是数据库、关系、元组
并发调度与可串行性:
可串行化调度:多个事务的并发执行是正确的,当且仅当其结果与按某次串行执行这些事务时结果相同
冲突可串行化调度:
- 冲突操作:不同事务对同一数据的读写操作 or 写写操作
- 冲突可串行调度:一个调度保证冲突操作次序不变的情况下,通过交换两个事务的不冲突操作的次序的到另一个调度,且新的调度是串行的
注意:调度是正确的 \(\Leftrightarrow\) 调度是可串行化的;冲突可串行化 \(\Rightarrow\) 可串行化,调整时尽量将相同事务的操作聚在一起
两段锁协议:对任何数据进行读写操作前都要申请封锁;释放一个封锁后,该事务不再获得任何其它锁(扩展 \(\rightarrow\) 收缩)
- 定理:若所有事务都遵循两段锁协议,则这些事务的并发调度都是可串行化的,即并发调度一定是正确的
注意:遵循两段锁协议的事务仍可能发生死锁;所有事务遵循两段锁协议 \(\Rightarrow\) 可串行化
其它并发控制方法:
事务隔离级别:SQL定义了 4 种隔离级别:“读未提交”、“读已提交”、“可重复读”、“可串行化”
时间戳排序协议:为每个事务分配一个全局唯一的时间戳,使所有事务单调排序,事务仅能访问排在其前的数据
注意:时间戳排序协议可产生冲突可串行化调度;若两个事务冲突,终止其中一个,将其回滚并重新调度,赋予新的时间戳乐观并发控制协议:分为以下三个阶段:
- 读操作:从数据库读入数据,在事务私有工作区写操作
- 验证:有效性检查测试是否满足所需的隔离限制,若检测失败则终止事务,否则继续写操作
- 写操作:将私有工作区中的更新数据写回到数据库中
注意:以上两种方法都是乐观并发控制,假设不会发生冲突,提交时才检查,做冲突处理
多版本控制:维护一个数据的多个物理版本,无锁并发控制
- 事务进行写操作时,产生该数据的一个新版本
- 事务进行读操作时,读取该事务开始时的数据的最新版本
注意:事务读操作无需等待且一定成功,但无法解决丢失更新的问题