第十章 事务管理


10.1 事务支持

事务:由单个用户或应用程序执行的,完成读取或更新数据库内容的一个或一串操作

  • 事务是数据库的逻辑操作单位
  • 从数据库的角度来看,应用程序的一次执行就是一个事务或者多个事务,若看成多个事务,在事务与事务之间只会出现非数据库操作
  • 在事务处理过程中,尽管我们允许数据库的一致性暂时遭到破坏,但是事务应该总是能将数据库从一种一致的状态转换到另一种一致的状态
  • 执行事务的执行:
  • 关键字
  • begin transaction: 开始事务
  • commit: 提交事务
  • roll back: 回滚事务
  • Note:如果程序中,没有用keywords,则整个程序作为一个事务,程序正确结束则commit,否则rollback
  • 输出
  • Success: 事务成功提交,数据库转换到一个新的一致状态
  • Failure: 事务中止(abort),数据库必须恢复到事务开始之前的一致状态(roll back)
  • 已提交的事务不能中止,如果已提交的事务有错误,那么只能通过额外的操作进行弥补
  • DBMS的事务子系统
  • 事物的基本性质(ACID):
  • 原子性(Atomicity):"全部或者都不"性质。事务是一个不可分割的单元,要么全部执行执行,要么都不执行。由DBMS的恢复子系统负责保证事务的原子性
  • 一致性(Consistency):事务必须将数据库从一种一致的状态转换到另一种一致的状态
  • 隔离性(Isolation):事务的执行是相互独立的。也就是说,未完成事务的中间结果对其他事务来说是不可见的。由并发控制子系统负责保证事务的隔离性
  • 持久性:完成成功(提交)的事务的结果要永久地记录在数据库中,不能因为以后的故障而丢失。由回复子系统保证事务的持久性

10.2 并发控制

并发控制:管理数据库上的并发操作以使之互不冲突的过程

并发控制的目的:以某种方式调度事务,能够避免事物之间的相互干扰

10.2.1 并发控制的必要性

  • 当两个或者多个用户同时访问数据库,并至少有一个用户在更新数据时,防止干扰
  • 可能两个事务分开执行时结果是正确的,但是交错并发执行时,得到不正确的结果
  • 并发可能带来的三类问题
  • Lost update problem – 丢失更新问题
  • 一位用户的更新操作明明已经完成,可是其结果却被另外一个用户的操作结果取代了
  • Example:
  • Uncommitted dependency problem – 未提交依赖(污读问题)
  • 如果允许一个事务看到另外一个未提交事务的中间结果,则会出现未提交依赖问题
  • Example:
  • Inconsistent analysis problem – 不一致分析问题(幻读问题)
  • 当事务正在从数据库中读取多个数据的值,但是另一个事务却在读取过程中修改了某些数据的值,这时就会出现不一致分析问题
  • Example:

10.2.2 可串行性与可恢复性

  • 调度:
  • 一组并发事务操作的序列,对于其中每个事务来说,该序列保留了该事务的所有操作的先后次序
  • 串行调度:每一个事务的操作都按顺序执行,且各事务之间的操作没有任何交叉的调度
  • 非串行调度:一组并发事务的操作相互交叉执行的调度
  • 可串行性:当且仅当(非串行)调度能够产生和某些串行执行相同的结果时,调度才是正确的。这样的调度被称为是可串行的
  • 如果两个事务都只是读取某一数据项,则它们之间不会相互冲突,这两个事务的执行次序无关紧要
  • 如果两个事务要读写的数据项完全没有交集,则它们不会相互冲突,这两个事务的执行次序无关紧要
  • 如果一个事务写某个数据项,而另一个事务要读或者写同一个数据项,则这两个事务的执行次序就非常重要
  • 可恢复性
  • 如果 Tj 读取了由 Ti 修改过的数据项,那么事务 Ti 的提交操作应该在事务 Tj 的提交操作之前。若调度中的每一对事务 Ti 和 Tj 都能满足上述要求,则该调度称为可恢复调度
  • 如果 Tj 在 Ti 之前提交,此时 Ti 回滚,则 Tj 造成的修改不可恢复
  • 为了保证可串行性和可恢复性的两种基本方法
  • Locking
  • Timestamping

10.2.4 加锁

  • 加锁:
  • 用来控制并发访问数据的过程。当一个事务正在访问数据库时,可以用锁拒绝其他事务的访问请求,从而避免产生不正确的结果
  • 基本特征:
  • 事务在对数据库进行读写操作之前必须获取一个共享锁或者互斥锁;锁可以阻止其他事务修改该事务正在操作的数据项,如果是互斥锁的话,甚至还可以阻止其他食物对该数据项的读取
  • 加锁的基本规则:
  • 共享锁:如果事务在数据项上加了共享锁,则事务只能读而不能修改该数据项
  • 可以有多个事务对同一数据项同时加共享锁
  • 互斥锁:如果事务在数据项上加了互斥锁,则事务既可以读也可以修改该数据项
  • 一次只能有一个事务对某一数据项加互斥锁
  • 锁的相容性:只有共享锁和共享锁相容
  • 加锁步骤:
  1. 根据事务类型,判断是需要共享锁还是互斥锁
  2. 如果数据项没有被其他事务加锁,则允许事务对其加锁
  3. 如果数据项已经被加锁,则判断当前的加锁请求是否和已经存在的锁相容。如果对已经加上共享锁的数据项请求加上共享锁,则请求被认可,否则事务必须等待,直到现有的锁被释放
  • 三个上锁等级
  • 一级:只能加共享锁,事务结束时释放 --> 解决丢失更新的问题
  • 二级:共享锁读完数据就释放,互斥锁事务结束时才释放 --> 解决丢失更新和脏读的问题
  • 三级:共享锁和互斥锁都事务结束时释放 --> 解决丢失修改,脏读,幻读的问题
  • 两段锁协议
  • 如果事务中所有的加锁操作都出现在第一个解锁操作之前,则该事务是遵循两段锁协议的
  • 根据两段锁协议,可以将事务分为两个阶段:
  • 扩展阶段:在该阶段,事务可以获取它所需要的全部所=锁,但不能释放任何一个锁
  • 收缩阶段:事务可以释放它拥有的全部锁,但是不能再获取任何新的锁
  • 严格两段锁协议:
  • 级联回滚:
  • T16 依赖于 T15,T15 依赖于T14,如果 T14 释放锁一段时间后回滚,但此时 T15 和 T16 获得锁进行了事务操作,则也需要回滚,这种现象就是级联回滚
  • 级联回滚会导致大量工作被撤销,是一种不好的现象
  • 为了防止级联回滚,引入严格两段锁协议
  • 严格两段锁协议:在普通两段锁协议的前提下,直到事务结束才允许释放所有的锁

10.2.4 死锁

  • 定义:当两个或者多个事务相互等待对方释放自己已经占有的锁时产生的僵局
  • 只有一种方式打断死锁:终止一个或者多个事务
  • 死锁对用户应该是透明的,DBMS应该自动重启被终止的事务
  • 三种常用的解决死锁的方法:
  • 超时:
  • 事务等待锁时,有一个系统规定的等待时间上限
  • 如果超过等待时间上限还未获取到锁,则DBMS自动终止该事务并重启
  • 死锁预防:
  • DBMS检查事务是否会造成死锁,绝不会使死锁发生
  • 死锁预防的方式是加上时间戳,使得事务有序
  • Wait - Die算法:只允许一个较老的事务等待一个较新的事务,否则事务被撤销并重启
  • Wound - Wait算法:只允许一个较新的事务等待一个较老的事务,如果一个较老事务申请的锁被一个较新的事务占有,则较新的事务被撤销
  • 死锁检测和恢复:
  • DBMS允许死锁发生,但是会检测死锁并终止死锁
  • 死锁检测方法:画 wait-for 图(WFG),当且仅当 WFG 中有环时,才有死锁发生

10.2.5 时间戳

  • 时间戳:由DBMS创建的、标识事务的相对启动时间的、具有唯一性的标识符
  • 在事务开始执行时,可以简单地用系统时钟生成时间戳。更常见的方法是,每当有新的事务启动时,就将一个逻辑计数器的值加一
  • 时间戳技术:一种并发控制协议,它用以下方式确定事务的顺序 – 越早的事务,时间戳越小,在发生冲突时优先级越高
  • 当事务对某一数据项进行读或者写时,必须在时间戳较小的事务对该数据项的读写完成之后进行
  • 除了事务有时间戳,数据项也有时间戳,每个数据项具有读、写时间戳
  • 读时间戳:上一个事务对该数据项进行读的时间戳
  • 写时间戳:上一个事务对该数据项进行写的时间戳
  • 托马斯写规则
  • 通过对基本时间戳排序协议进行修改,放宽对冲突可串行化的要求,拒绝过时的写操作以以获得更高的并发性
  • 该规则修改了事务T的写操作规则:
  1. 事务T要求写数据项(x),而该数据项已经被一个较新的事务读过,则需要将事务T回滚,并以新的时间戳重启
  2. 事务T要求写数据项(x),而该数据项已经被一个较新的事务更新过,则事务T的写操作被忽略,事务T跳过写操作,继续执行。理解如下:
  • 实际情况:一个较新事务 A 在 T 之前修改了数据项 x,此时忽略 T 的写操作
  • 假设情况:事务 T 在 A 之前修改了数据项 x。A之后修改,从而导致 T 的修改被覆盖
  • 可以发现,实际情况和假设情况的结果一样,而且假设情况满足可串行性,从而验证了托马斯写协议是正确的
  1. 否则,与此前一样,写操作可以被执行。然后置write_timestamp(x) = ts(T)

10.2.6 数据项粒度

受到保护的数据项的大小,是并发控制协议中受到保护的基本单位

  • 数据项通常有如下选择,按照粒度从粗到细排列,细粒度是指比较小的数据项,粗粒度是指较大的数据项:
  • 整个数据库
  • 一个文件
  • 一页
  • 一条记录
  • 记录的一个字段的值
  • 在一次操作中可以被加锁的数据项的大小(或者说粒度),对并发控制算法的全局性能有很大的影响
  • 加锁时,数据项粒度的选择:
  • 粒度过大:会导致并发性降低
  • 粒度过小:需要存储更多的加锁信息
  • 一些系统会根据事务的需求,自动增大锁的控制范围(比如当事务需要的数据不止一页时,系统会自动将锁的控制范围增大到整个文件)
  • 粒度的层次:
  • 如果对某节点加锁,则该节点下面的所有子孙节点都会被加锁
  • 事务对某节点加锁
  • 如果该节点有父节点被加锁,则需要检查从根节点到请求节点的路径,确定其祖先节点的加锁情况,如果冲突则拒绝对请求节点加锁
  • 如果该节点有子孙节点被加锁,则需要检查所有子孙节点的加锁情况,如果冲突,拒绝对请求节点的加锁
  • 意向锁:当 一个节点加锁时,该节点的所有祖先节点就都加了对应的意向锁
  • IS 意向读锁:意味着有子孙节点被加了读锁
  • IX 意向写锁:意味着有子孙节点被加了写锁
  • 锁的相容性:

10.3 数据库恢复

数据库恢复:在发生故障时,将数据库还原到正确状态的过程

10.3.1 恢复的必要性

  • 四种不同类型的数据存储介质
  1. 主存储器:易失性存储器
  2. 磁盘:联机非易失存储
  3. 磁带:非联机非易失存储介质
  4. 光盘:便宜、更快,且支持随机访问
  • 数据库故障类型
  1. 系统崩溃
  2. 介质故障
  3. 应用软件错误
  4. 自然物理灾害
  5. 疏忽
  6. 蓄意破坏

10.3.2 事务和恢复

  • 事务是数据库系统进行恢复的基本单位
  • 故障发生时,由恢复管理器负责保证事务ACID特性中的原子性持久性
  • 缓冲区:
  • 用于在内存和二级存储器之间传递数据,只有缓冲区中的数据被刷新到二级存储器中,更新操作才能被看作是永久性的
  • 缓冲区到数据库的刷新操作可以被某个特殊的命令触发(比如事务提交),也可以在缓冲区满时自动进行
  • 缓冲区到二级存储器的显式写被称为强制写
  • 重做 – redo
  • 如果在写缓冲区和将缓冲区数据写到二级存储器之间发生了故障,恢复管理器必须确定当故障发生时正在执行写操作的事务的状态。如果该事务已经执行了提交,则为了保证永久性,恢复管理器必须重做该事务对数据库的所有修改操作,也称为向前滚动
  • 撤销 – undo
  • 如果在故障发生时该事务还未提交,则回复管理器必须撤销,即回滚该事务对数据库已做的所有修改,以保证事务的原子性。如果只有一个事务需要被撤销,则称为部分撤销,例如事务回滚并重启。当所有的活跃事务都必须被撤销时,称为全局撤销。
  • 当页被写回磁盘时,会用到下列术语:
  • 偷窥策略:允许缓冲区管理器在事务提交前将缓冲区写回磁盘,也就是说,缓冲区管理器从事务那里偷了一页。与其对应的是非偷窥策略
  • 采用非偷窥策略时,无需撤销一个已撤消事务所作的更改,因为这些修改还未被写回磁盘
  • 强制策略:保证事务提交时,事务更新的所有页立即被写回磁盘。与其对应的是非强制策略。
  • 使用强制策略时,就无需在系统崩溃时重做一个已经提交事务所作的更改,因为所有的更新在提交时就已经被写回磁盘

10.3.3 恢复机制

  • 备份机制
  • DBMS应该提供某种机制,使得系统能够定期备份数据库和日志文件,而且除非必要,否则就不应先停止系统的运行再备份
  • 日志机制
  • 日志中记录了对数据库的所有更新信息。包括以下数据:
  • 事务记录:
  • 事务标识符
  • 日志记录的类型
  • 对数据库的动作所影响到的数据项的标识符
  • 数据项的前像,即数据项被修改之前的值(仅指更新和删除操作)
  • 数据项的后像,即数据项被修改之后的值(仅插入和更新操作)
  • 日志管理信息,比如指向事务的前一条或下一条日志记录的指针
  • 检查点记录
  • Example:
  • 检查点机制
  • 检查点:数据库与事务日志文件之间的同步点,在该点上所有的缓冲区都被强制写到二级存储器
  • 背景:单纯使用日志文件时,不知道应该在日志文件中向前搜索多远才可以不用重做那些已经被安全写到数据库的事务
  • DBMS在预定义的时刻设置检查点,并执行以下操作:
  • 将内存中的所有日志记录写到二级存储器
  • 将数据库缓冲区中所有被修改过的块写到二级存储器
  • 将一个检查点记录写到日志文件。该记录包含所有在检查点时刻活跃事务 的标识符
  • 恢复管理器

10.3.4 恢复技术

  • 执行那种恢复程序,主要依赖于数据库受损的程度。主要考虑以下两种情况:
  • 如果数据库遭到了很严重的损坏,比如磁头损坏并且破坏了数据库,则除了必须利用数据库最近的副本进行恢复以外,还要利用日志文件重做已经提交了的事务
  • 如果数据库并没有受到物理破坏,但是处于不一致的状态,比如在事务执行时系统崩溃,则必须撤销引发不一致的修改操作,必要时需要回滚并重启某些事务
  • 针对数据库并没有受到物理破坏,但处于不一致的状态,进行恢复的技术主要有两种:
  • 延迟修改恢复技术:
  • 采用延迟修改恢复协议时,直到事务提交以后,修改结果才会被写到数据库。
  • 如果事务在提交前失败,则不会修改数据库,因此无需对事务的修改操作进行撤销。
  • 然而对于已经提交的修改操作有必要进行重做,因为这些修改还未被写入数据库
  • 立即修改恢复技术:
  • 采用立即修改立即修复协议时,更新一旦发生就立即施加到数据库,而无需等到提交时刻。
  • 可能需要重做已提交的事务
  • 可能需要撤销在出故障时还未来得及提交的事务
  • 影像页技术
  • 影像页技术在事务的生存期为其维持了两张页表:当前页表和影像页表
  • 当事务刚启动时,两张页表是相同的。此后,影像页表不再改变,并在系统故障时用于恢复数据库。
  • 在事务的执行过程中,当前页表用于记录对数据库的所有更新。当事务完成时,当前页表转变为影像页表。
  • 优点:
  • 没有维持日志文件的开销
  • 不需要撤销或者重做,恢复起来也相当快速
  • 缺点,比如数据分裂以及需要周期性地进行无用单元回收,以回收那些不在被访问的块
    刻。
  • 可能需要重做已提交的事务
  • 可能需要撤销在出故障时还未来得及提交的事务
  • 影像页技术
  • 影像页技术在事务的生存期为其维持了两张页表:当前页表和影像页表
  • 当事务刚启动时,两张页表是相同的。此后,影像页表不再改变,并在系统故障时用于恢复数据库。
  • 在事务的执行过程中,当前页表用于记录对数据库的所有更新。当事务完成时,当前页表转变为影像页表。
  • 优点:
  • 没有维持日志文件的开销
  • 不需要撤销或者重做,恢复起来也相当快速
  • 缺点,比如数据分裂以及需要周期性地进行无用单元回收,以回收那些不在被访问的块