第5章 事务及事务管理

1. 事务

  • DBMS的事务:
    • 事务由SQL语句组成,DBMS将SQL语句转换为内部操作,这些操作是一个整体,不能分割。DBMS的事务管理子系统负责事务的处理。
    • 从数据库读取数据、向数据库写入数据

1.1 事务的特性

  1. 原子性(Atomicity)
  • 事务的所有操作操作是一个逻辑上不可分割的单位,即要么所有的操作都顺利完成,要么一个操作也不做,绝不能只完成了部分操作、而还有一些操作没有完成。
  • DVMS的恢复子系统采用日志和备份技术保证事务的原子性
  1. 一致性(Consistency)
  • 数据库处于一致性状态是指数据库的数据满足各种完整性。事务具有一致性就是事务不能破坏数据库的各种完整性。
  • 不能破坏显式的约束:各类完整性约束,由 DBMS负责;也不能破坏隐式的约束:应用的约束,由程序员负责
  • 执行每一条SQL语句后,要捕获其返回码,判断语句是否正常执行,如果出现问题,就必须使用ROLLBACK语句撤销事务,否则会破坏数据库的一致性
  1. 隔离性(Isolation)
  • 隔离性是指无论同时有多少个事务在执行,DBMS均保证事物之间互不干扰,同一时刻就像只有一个事务在运行一样
  • DBMS的并发控制子系统采用共享锁/排他锁等各种技术来满足事务的隔离性
  1. 持久性(Durability)
  • 事务一旦提交,即执行了ROLLBACK或COMMIT语句,无论出现什么情况,即使突然掉电或者操作系统崩溃,DBMS也确保完成指定的任务,即ROLLBACK保证撤销事务对数据库所做的全部操作,COMMIT保证把全部的操作结果保存到数据库
  • DBMS的恢复子系统采用日志和备份技术保证事务的持久性

1.2 定义事务的 SQL 语句

-- 1. 查询张三账户余额
select * from account where name = '张三';
-- 2. 将张三账户余额-1000
update account set money = money - 1000 where name = '张三';
-- 此语句出错后张三钱减少但是李四钱没有增加
模拟sql语句错误
-- 3. 将李四账户余额+1000
update account set money = money + 1000 where name = '李四';

-- 查看事务提交方式
SELECT @@AUTOCOMMIT;
-- 设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
SET @@AUTOCOMMIT = 0;
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;

-- 设置手动提交后上面代码改为:
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
开启事务:START TRANSACTION 或 BEGIN TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;

操作实例:
start transaction;
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
  • 事务是类似于具有一个入口、两个出口的一种控制结构。COMMIT是一个出口,ROLLBACK是另一个出口。

  • 设置保存点语句:SAVEPOINT

  • 撤销保存点语句:RELEASE SAVEPOINT

  • 设置事务特性语句:SET TRANSACTION

  • 设置事务限制语句:SET CONSTRAINTS

  • 例:假设为Course表增加一个Limit列,用于存储允许选修这门课程的最大人数。当有一个学生报名选修这门课程时,将Limit列的值减1。如果课程的Limit列的值等于0,就不允许任何学生再选修这门课程。

2. 日志、备份和恢复技术

  • DBMS的恢复子系统通过保存冗余数据,在必要时撤销(UNDO) 或重做(REDO) 一个或多个事务,
    使数据库始终处于一致性状态。

2.1 故障种类

  • 在系统运行期间,DBMS在内存开辟了系统缓冲区,用于临时存放从数据库读出的数据和要写回数据库的数据,并给每个事务在内存中建立各自的工作区
  1. 事务故障:
  • 指事务在运行过程中,出现运算溢出、违反了某些完整性、某些应用程序发生错误,使事务不能继续执行下去的情况称为事务故障
  • 出现事务故障会造成事务的一部分操作已经完成并且操作结果也保存到了数据库,违反了事务的原子性要求,使数据库处于不一致性状态
  1. 系统故障:
  • 指系统在运行过程中,由于某种原因,如操作系统或DBMS代码错误、操作员操作失误、特定类型的硬件错误、突然停电等造成系统停止运行,丢失了系统缓冲区的数据,而存储在磁盘的数据未受到影响
  • 有些事务只完成了部分操作,破坏了原子性
  • 有些数据完成了全部操作,但事务的处理结果保存在缓冲区,没有反映到磁盘上的数据库,破坏了持久性
  1. 介质故障:
  • 指系统在运行过程中,由于某种硬件故障,如磁盘损坏、磁头碰撞或由于操作系统的某种潜在错误、瞬时强磁场干扰,使存储在外存的数据部分损失或全部损失的故障,发生的可能性小得多,但破坏性更大
  • 正在运行的事务都被中止,系统缓冲区的数据无法写入磁盘,存储在磁盘的数据部分或全部丢失。

2.2 应对措施

  • 不同类型的故障需要采取不同的恢复操作,这些操作从原理上讲都是利用存储在其他地方的冗余数据来重建数据库中已经被破坏或已经不正确的那部分数据
  • 下面介绍如何建立冗余数据

2.2.1 日志文件

  • 事务由一系列对数据库的读写操作组成,按照操作执行的先后次序,记录事务执行的所有对数据库的写操作(更新操作),就构成了事务的日志文件。
  1. 日志文件的格式和内容
  • 日志文件从逻辑上来看由若干条记录构成,这些记录叫作日志记录,同一个事务的日志记录被组织成一个链表

  • 事务日志文件由若干记录组成记录有3种类型。

    1. 记录事务的开始
        主要记录事务的内部标识和开始时间。(BEGIN)
    2. 记录事务的结束
        主要记录事务的内部标识和结束时间。(COMMIT、ROLLBACK)
    3. 记录事务的更新操作(INSERT、UPDATE)
      事务标识(标明是哪个事务)
      操作的类型(插入、删除或修改)
      操作对象(记录内部标识)
      更新前数据的旧值(对于插入操作而言,此项为空值)
      更新后数据的新值(对于删除操作而言,此项为空值)
    


2. 登记日志文件

  • 先把日志记录写入日志文件,后把数据写入数据库。
  • 先写日志、后写数据的原则:
    1. 事务把更新后的数据和形成的日志记录写入系统缓冲区
    2. 将日志记录写入日志文件
    3. 将更新后的数据写入数据库

2.2.2 数据库备份

  • 为了处理介质故障,需要由数据库管理员定期将数据库和日志文件复制到磁带或磁盘,并将这些备份的数据文本妥善保存起来,当数据库遭到破坏时,可以将后备副本重新装入,恢复数据库
  • 数据库和日志文件备份要使用DBMS提供的实用程序完成,而不能使用操作系统的copy命令
  • 制作备份的过程称为转储
  • 转储可以分为海量转储(转储整个数据库)和增量转储(转储上次以来发生变化的部分数据库)
  • 转储还可分为静态转储(在转储期间,DBMS停止接收新的事务)和动态转储(在转储期间,DBMS继续接收新的事务)

2.3 恢复过程

  • 下面讨论如何如何利用冗余数据进行恢复操作

2.3.1 事务故障的恢复

  • 事务故障是指事务未运行至正常中止点前被DBMS或用户撤销,这时恢复子系统对此事务做UNDO(撤销)处理。

  • DBMS自动完成事务故障的恢复,其过程为:

    1. 反向阅读日志文件,找出该事务的所有更新操作
    2. 对每一个更新操作做它的逆操作
        若日志记录是插入操作,则做删除操作;
        若日志记录是删除操作,则做插入操作;
        若是修改操作,则用修改前的值代替修改后的值
    3. 遇到BEGIN日志记录,事务故障恢复完成。 
    

2.3.2 系统故障的恢复

  • 系统故障发生时,造成数据库处于不一致性状态的原因有两个:
  1. 一些未完成事务对数据库的更新已写入数据库
  2. 一些已提交事务对数据库的更新还留在系统缓冲区,没来得及写入数据库
  • 发生系统故障后,需要重启系统,此时进行系统恢复,基本恢复算法分为两步:

    1. 根据日志文件建立重做事务队列和撤销事务队列
        从头扫描日志文件,将既有BEGIN又有COMMIT或ROLLBACK的事务 加入重做(REDO)队列,将只有BEGIN的事务加入撤销(UNDO)队列
    2. 对UNDO队列的事务进行UNDO处理,对REDO队列的事务进行REDO处理
    

2.3.3 介质故障的恢复

  • 发生介质故障时,恢复操作可分为3步进行:
  1. 装入转储的数据库副本,使数据库恢复到转储时的一致状态。

  2. 装入转储后备份的第一个日志文件。

    ① 读日志文件,找出已提交的事务,按提交次序的先后将其记入REDO队列。

    ② 重做REDO队列的每个事务的所有更新操作。

  3. 装入下一个日志文件重复步骤2,直至处理完所有的日志文件,这时数据库恢复至故障前一时刻的一致性状态。

3. 并发控制技术

  • 事务的并发执行可能引发的异常:

    • 丢失修改
    • 读脏数据
    • 不可重复读
    • 幻影现象
  • 为了防止并发执行产生的问题,DBMS需要具备并发控制功能,并发控制由DBMS的调度器、事务管理器以及存储子系统协同实现

  • 调度的含义:重新安排到达调度器的读写操作的执行次序,而不是“先来先服务”

  • 并发控制常用的方法:

    • 封锁法
    • 时间印法
    • 乐观法

3.1 并发引发的异常

3.1.1 读脏数据

  1. 脏读: 一个事务读到另一个事务还没有提交的数据

3.1.2 丢失修改

3.1.3 不可重复读

  1. 不可重复读: 一个事务先后读取同一条记录, 但两次读取的数据不同

3.1.4 幻读

  1. 幻读: 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在
  2. 幻影是指满足一个谓词条件的集合发生了变化,即这个集合增加了新的成员或失去了原有的成员,可以使用后面提到的谓词锁解决
  • 可串行化调度:串行调度是正确的,执行结果等价于串行调度的调度也是正确的,这样的调度叫作可串行化调度

3.2 封锁技术

3.2.1 冲突操作

  • 冲突操作是指两个不同的事务操作同一个数据对象(或同一个谓词定义的数据对象集合),并且其中有写操作
  • 4类冲突操作:
  1. W1(A)和W2(A);写写冲突
  2. W2(A)和W1(A);写写冲突
  3. W1(A)和R2(A) ;读写冲突
  4. R1(A)和W2(A) ;读写冲突

3.2.2 S锁和X锁

  • 封锁技术使用锁控制冲突操作的执行次序,两种常用锁:S锁(共享锁,Share Locks)和X锁(排他锁,Exclusive Locks)
  1. S锁:

    • 又称为共享锁、读锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能对A加S锁,而不能加X锁,直到T释放A上的S锁。保证了其他事务可以读A,但在T释放A上的S锁之前,不能对A做任何修改
  2. X锁:

    • 又称为排他锁、写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他事务不能对A加任何类型的锁,直到T释放A上的X锁。保证了其他事务在T释放A上的锁之前,不能读取和修改A

3.2.3 两阶段封锁协议

  • 协议分为加锁和解锁两个阶段
  • 读操作前,先申请S锁,批准后,执行读操作
  • 写操作前,先申请X锁,批准后,执行写操作
  • COMMIT和ABORT报告事务结束,集中释放锁
  • 假设事务T1和事务T2的操作次序如上(b)图所示,采用两阶段封锁协议后,事务各操作的实际次序如上表所示。在t1时刻,事务T1的 W(A)操作到达调度器,加 X锁成功,同时完成 W(A)操作。在t2时刻,事务T1的 R(limit)操作到达调度器,加S锁成功,完成读操作,得到 Limit=80。在t3时刻,事务T2的 W(A)操作到达调度器,加X锁未成功,因为在操作对象上已经有一个X锁,因此,事务T2被挂起。在t6时刻,事务T1的 W(Limit=79)操作到达调度器,加X锁成功,立刻执行该操作。在t7时刻,调度器接收到了事务T1的COMMIT请求,释放T1获取的所有锁。这时,T2的对A的加锁操作XLOCK(A)成功,事务T2的其他操作被依次执行
  • 显然调度器改变了事务操作的执行次序,得到了一个串行化调度
  • 有证明,如果调度器按照两阶段封锁协议来调度的话,则产生的调度一定是可串行化调度,因为这样的调度是通过协调冲突操作得到,所以又叫作冲突可串行化调度冲突可串行化调度是可串行化调度的子集即事务遵守两阶段协议是可串行化调度的充分而非必要条件

3.2.4 谓词锁

  • 加锁只能对数据库中已经存在的对象加锁,不能对不存在的对象加锁
  • S锁和X锁的封锁对象是数据库已有的数据对象,采用这样的两阶段封锁协议不能排除幻影异常,因为引起幻影的原因是所要操作的数据集合发生了变化
  • 谓词锁:使S锁和X锁不仅能锁定具体的数据对象,还能锁定一个谓词定义的集合,就能排除幻影异常

3.2.5 封锁粒度和意向锁

  1. 封锁粒度:

    • 数据对象的大小称为封锁粒度
    • 加锁对象粒度大,并发度低,锁表小,加锁代价小
    • 加锁对象粒度小,并发度大,锁表大,加锁代价大
  2. 意向锁

    • 如果这些数据对象之间具有层次关系(构成了一棵树)或数据对象之间构成了一个有向无环图,还可以增加意向锁
    • 意向读锁IS锁
    • 意向写锁IX锁
    • 读意向写锁SIX锁
    • 增加这些意向锁可以减少加锁的数量和并发控制系统的开销

3.2.6 死锁问题

  • 原因:由于采用加锁手段进行调度,所以会产生死锁现象

  • 现象:

    事务T1已经获得了对数据对象A的锁,又申请对数据对象B加锁,但没有获得批准,处于B的等待队列。
    事务T2已经获得了对数据对象B的锁,又申请对数据对象A加锁,但没有获得批准,处于A的等待队列。
    两个事务都处于无限等待中,不能继续执行下去,称为死锁问题。 
    

  • 策略:

    一般来说,可能有多个事务因为互相等待其他事务持有的锁而陷入死锁状态,此时,DBMS会根据一定的策略选择一个或多个事务,强行中断其执行,回滚它或它们的所有操作,以便打破死锁状态,让其他事务继续执行下去。 
    

3.3 事务的隔离级别

  • DBMS的并发控制子系统保证了事务的隔离性

  • 因为强制每个事务执行期间都需要加锁、解锁操作,所以会增加系统的开销,而且事务有时会处于等待状态,延缓了事务的执行。故使用隔离级别设置语句加快事务执行

    SET TRANSACTION ISOLATION LEVEL
           {READ UNCOMMITTED  未提交读
            | READ COMMITTED 已提交读
            | REPEATABLE READ 可重复读
            | SERIALIZABLE 
           }
    


    image

查看事务隔离级别:
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别:
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效
posted @ 2024-12-17 15:28  awei040519  阅读(120)  评论(0)    收藏  举报