『database-8』transaction

事务处理技术

一、事务概念

  • 事务的特性:ACID

    • 原子性:事务中的所有操作要么都做,要么都不做
    • 一致性:事务执行的结果必须是从一个一致性状态到另一个一致性状态
    • 隔离性:一个事务的执行不能被其它事务干扰,并发执行的各事务间不能相互干扰
    • 持久性:一个事务一旦提交,对数据库的影响必须是永久的

    注意:原子性 和 持久性 由恢复机制实现,隔离性由并发控制实现,一致性由事务的原子性保证


二、数据库恢复技术

  • 数据库恢复:把数据库从错误状态恢复到某个正常状态的功能,保证了事务原子性,确保故障时可以恢复到正确状态

  • 事务内部故障:分为可预期和不可预期故障(应用程序无法处理),意味着既没有提交、也没有显式回滚

  • 系统故障:造成系统停止运转,使所有事务异常终止,但不会破坏数据库

  • 介质故障:外存故障,如磁盘损坏,这会破坏数据库

  • 计算机病毒:人为破坏


  • 数据转储:DBA定期将整个数据库复制到另一个磁盘上保存起来

    • 静态转储:无事务运行时转储,转储时不允许对数据库做任何读取或修改;保证副本一致性,但可用性低

    • 动态转储:转储时允许对数据库做存取或修改;不影响可用性,但要把转储期对数据库的修改记录在日志中


    • 海量转储:一次性转储全部数据库

    • 增量转储:每次只转储上次转储后更新过的内容

    • 日志文件:一条日志记录包括“事务标识”、“操作类型”、“操作对象”、“更新前旧值”、“更新后新值” 以数据块为单位的日志文件,日志记录包括 事务标识 + 被更新的数据块

      • 动态转储:后备副本 + 日志文件 才能恢复数据库
      • 静态转储:通过 日志文件 恢复转储结束 \(\Rightarrow\) 故障点间 的事务

      注意:必须按照并发事务的执行先后顺序写入日志文件,且要先写日志、后写数据库

    • 事务故障恢复:撤销操作,即反向扫描日志文件,对各更新操作执行逆操作,直至读到事务开始标志

    • 系统故障恢复:撤销未完成的事务 + 重做已完成的事务,正向扫描日志文件:

      • 将故障前已提交的事务加入到重做队列
      • 将故障时未完成的事务加入到撤销队列
    • 介质故障恢复:

      1. 装入最新的数据库后备副本,使数据库恢复到最近一次转储时的一致性状态
      2. 装入转储后的日志文件副本,重做已经完成了的事务

    • 检查点技术:在日志文件中新增一类记录(检查点记录),新增重新开始文件

      • 检查点记录:建立该记录时刻所有正在执行的事务清单 + 这些事务最近一个日志记录的地址
      • 重新开始文件:记录各检查点记录在日志文件中的地址
    • 动态维护日志文件:建立检查点 + 保存数据库状态

      1. 将日志缓存中的所有日志记录写回日志文件
      2. 在日志文件上写入一个检查点记录(先写日志)
      3. 将当前数据缓存中的所有数据记录写回磁盘数据库(后写数据库)
      4. 把检查点记录在日志文件中的地址记录在重新开始文件
    • 检查点技术的恢复

      1. 根据重新开始文件的最后一个地址,找到日志中最后一个检查点记录
      2. 把最后一个检查点建立时刻所有正在运行的事务暂时加入到 UNDO-LIST
      3. 从检查点正向扫描日志文件,若该任务新开始,则将其暂时加到 UNDO;若该任务已提交,则将其从 UNDO 移动到 REDO
      4. 对 UNDO 中的任务执行撤销操作,对 REDO 中的任务执行重做操作

    • 数据库镜像:每当主数据库更新时,DBMS自动把整个数据库复制到另一个磁盘上

      • 数据库恢复:数据库故障时,镜像磁盘可供继续使用、以及数据恢复(不必重装数据库副本)
      • 数据库可用:正常情况下,一个用户对数据库添加排他锁时,其他用户可读取镜像数据库上的数据

    三、数据库并发控制

    • 并发操作导致的数据不一致性:

      • 丢失更新:两个事务读取同一数据并先后修改提交,T2的提交破坏了T1的提交结果
      • 读脏数据:T2读取了T1修改的数据,但T1的修改数据被撤销,导致T2读到的数据不一致
      • 不能重复读:T1读取数据后,T2更新数据,T1再次读数据时发现读取结果无法复现
    • 封锁:排他锁(X)和 共享锁(S)

      • 一级封锁协议:事务在修改数据前必须加 X 锁,直至事务结束后才释放
        保证不丢失修改(不会被干扰覆盖),但由于读数据不需要申请锁,故不保证避免脏读 + 可重复读
      • 二级封锁协议:在一级封锁协议的基础上,读数据前先加 S 锁,读完即释放
        进一步保证不读“脏数据”(修改事务结束前不能读),但不保证可重复读(应该在事务中的所有读操作结束后才释放)
      • 三级封锁协议:在一级封锁协议的基础上,读数据前先加 S 锁,直至事务结束才释放
        进一步保证可重复读

    • 活锁:很长时间等待其它事务释放锁,轮不到自己

      • 预防方法:先来先服务
    • 死锁:互相持有锁,无法结束,解决方案如下:

      • 预防死锁:
        • 一次封锁法:一次性把事务所有资源全部加锁,否则不能执行,并发度较低
        • 顺序封锁法:预先对各数据对象规定一个封锁顺序,实现难度较大
      • 检测死锁:
        • 超时法:等待时间超过规定期限,就认为发生死锁
        • 等待图法:若等待图中存在有向回路,说明出现死锁

    • 封锁粒度:封锁粒度大,并发度低,封锁开销小;封锁粒度小,并发度高。封锁开销大

      • 多粒度树:根表示整个数据库,自顶向下是数据库、关系、元组
        • 显示封锁:直接加到对象上的锁
        • 隐式封锁:该对象上没有独立加锁,但其上级节点加锁导致该对象加了同类型的锁
      • 封锁检查:对某个对象加锁,需要同时做以下检查
        • 是否与其本身上的显示封锁冲突
        • 是否与其隐式封锁冲突(检查其所有上级节点)
        • 是否与其下级节点显示封锁冲突(检查其所有下级节点)
      • 意向锁:把那个对称表记住

    • 并发调度与可串行性:

      • 可串行化调度:多个事务的并发执行是正确的,当且仅当其结果与按某次串行执行这些事务时结果相同

      • 冲突可串行化调度:

        • 冲突操作:不同事务对同一数据的读写操作 or 写写操作
        • 冲突可串行调度:一个调度保证冲突操作次序不变的情况下,通过交换两个事务的不冲突操作的次序的到另一个调度,且新的调度是串行

        注意:调度是正确的 \(\Leftrightarrow\) 调度是可串行化的;冲突可串行化 \(\Rightarrow\) 可串行化,调整时尽量将相同事务的操作聚在一起

    • 两段锁协议:对任何数据进行读写操作前都要申请封锁;释放一个封锁后,该事务不再获得任何其它锁(扩展 \(\rightarrow\) 收缩)

      • 定理:若所有事务都遵循两段锁协议,则这些事务的并发调度都是可串行化的,即并发调度一定是正确

      注意:遵循两段锁协议的事务仍可能发生死锁;所有事务遵循两段锁协议 \(\Rightarrow\) 可串行化


    • 其它并发控制方法:

      • 事务隔离级别:SQL定义了 4 种隔离级别:“读未提交”、“读已提交”、“可重复读”、“可串行化”

      • 时间戳排序协议:为每个事务分配一个全局唯一的时间戳,使所有事务单调排序,事务仅能访问排在其前的数据
        注意:时间戳排序协议可产生冲突可串行化调度;若两个事务冲突,终止其中一个,将其回滚并重新调度,赋予新的时间戳

      • 乐观并发控制协议:分为以下三个阶段:

        1. 读操作:从数据库读入数据,在事务私有工作区写操作
        2. 验证:有效性检查测试是否满足所需的隔离限制,若检测失败则终止事务,否则继续写操作
        3. 写操作:将私有工作区中的更新数据写回到数据库中

        注意:以上两种方法都是乐观并发控制,假设不会发生冲突,提交时才检查,做冲突处理

      • 多版本控制:维护一个数据的多个物理版本,无锁并发控制

        • 事务进行写操作时,产生该数据的一个新版本
        • 事务进行读操作时,读取该事务开始时的数据的最新版本

        注意:事务读操作无需等待且一定成功,但无法解决丢失更新的问题


『database-8』transaction
http://larry0454.github.io/2023/11/16/database/transaction/
Author
WangLe
Posted on
November 16, 2023
Licensed under