7.1 事务
7.1.1 事务定义
1.事务是用户定义的一个数据操作序列,这些操作要么全部执行、要么全部不执行,是一个不可分割的工作单元。
事务是恢复和并发控制的基本单位
事务的两种方式:
7.1.2 事务处理模型
1.ISO事务处理模型:
明尾暗头――事务的开头是隐含的,事务的结束有明确标记
事务起始位置:程序的首条SQL语句或事务结束符后的第一条语句。
事务结束符:
COMMIT:事务成功结束符,
ROLLBACK:事务失败结束符,
事务终止位置:
在程序正常结束处或COMMIT语句处成功终止;
在程序出错处或或ROLLBACK处失败终止。
UPDATE 支付表 SET 帐户总额 = 帐户总额 - n WHERE 帐户名 = ‘A’ UPDATE 支付表 SET 帐户总额 = 帐户总额 + n WHERE 帐户名 = ‘B’ COMMIT
2.T-SQL事务处理模型:
每个事务都有显式的开始和结束标记。
事务的开始标记是:
BEGIN TRANSACTION | TRAN
事务的结束标记为:
COMMIT [TRANSACTION|TRAN]
ROLLBACK [TRANSACTION|TRAN]
BEGIN TRANSACTION UPDATE 支付表 SET 帐户总额 = 帐户总额-n WHERE 帐户名 = ‘A’ UPDATE 支付表 SET 帐户总额 = 帐户总额+n WHERE 帐户名 = ‘B’ COMMIT
7.1.3 事务的特性:ACID
原子性:事务是一个完整的操作; 对于其数据修改,要么全都执行,要么全都不执行。
一致性:事务在完成时,必须使所有的数据都保持一致状态。
隔离性:对数据进行修改的所有并发事务是彼此隔离的。
持久性:事务完成之后,它对于系统的影响是永久性的。即使出现故障也将一直保持。
保证事务的ACID特性是事务处理的重要任务。
事务的ACID特性可能遭到破坏的因素有:
多个事务并行运行时,不同事务的操作有交叉情况。(并发控制)
事务在运行过程中被强迫停止。(数据恢复)
7.2 并发操作引发的问题
7.2.1 并发控制
数据库系统的一个主要特点是多用户共享数据库资源。
若对多用户的并发操作不加控制,就会造成数据存、取的错误,破坏数据的一致性和完整性。
7.2.2 并发操作引发的问题
1.丢失数据修改:指事务T1与事务T2从数据库中读入同一数据并修改,事务T2的提交结果破坏了事务T1提交的结果,导致事务T1的修改被丢失。
2.读“脏”数据:事务T1修改后的数据值被事务T2读取,然后T1由于某种原因被撤销,其修改的数据值也恢复原值,于是T2读到的数据值与数据库中的数据值不一致,称为读“脏”数据。
3.不可重复读:指事务Tl从数据库读取某些数据后,事务T2对其进行了修改(也可能是删除或插入),当事务T1再次读取该数据时,得到与上次不一致的值。
产生上述数据不一致现象的主要原因就是并发操作破坏了事务的隔离性。
7.3 基本封锁类型
7.3.1 并发控制措施
控制目标:事务运行过程中尽可能隔离事务外操作对本事务数据环境的影响。
在数据库环境下,并发控制的主要方式是封锁机制,即加锁(Locking)。
所谓封锁,就是指事务在对某个数据对象进行操作之前,先要对其申请加锁。加锁成功后,即对该数据对象具有一定的控制权。
排它锁(X):又称写锁,一旦一事务获得了对某一数据的排它锁,则任何其他事务再不能对该数据加任何类型的锁。其他事务只能进入等待状态,直到第一个事务撤销了对该数据的封锁。
共享锁(S):又称读锁,指对于读操作(检索)来说,可以多个事务同时获得共享锁,但阻止其他事务对已获得共享锁的数据进行排它封锁。
7.3.2 排它锁和共享锁的控制方式
7.4 封锁协议
7.4.1 一级封锁协议
封锁协议:在运用X锁和S锁对数据对象进行加锁时,还需要约定一些规则,如何时申请X锁或S锁、持锁时间、何时释放锁等,这些规则为封锁协议或加锁协议(Locking Protocol)。
对封锁方式规定不同的规则,就形成了各种不同级别的封锁协议。 不同级别的封锁协议达到的系统一致性级别不同。
对事务T要修改的数据加X锁,直到事务结束(包括正常结束和非正常结束)时才释放
一级封锁协议可以防止丢失修改,并保证事务T是可恢复的
不能保证不读“脏数据” ;不能保证可重复读
7.4.2 二级封锁协议
二级封锁协议:一级封锁协议加上对事务T对要读取的数据加S锁,读完后即释放S锁。
7.4.3 三级封锁协议
一级封锁协议加上事务T对要读取的数据加S锁,并直到事务结束才释放
除了可以防止丢失修改和不读“脏”数据之外,还进一步防止了不可重复读
7.4.4 不同级别的封锁协议总结
7.5 死锁
两个事务相互等待对方先释放资源,则会造成死锁。
7.5.1 预防死锁的方法
1.一次封锁法:每个事务一次将所有要使用的数据全部加锁。
会降低系统并发度
2. 顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按这个顺序封锁 。
维护数据对象的封锁顺序成本很高
很难事先确定每个事务的所有封锁对象
多采用诊断并解除死锁的方法
7.5.2 死锁的诊断与解除
诊断:超时法、事务等待图法
解除:撤销处理代价最小的事务
7.6 并发调度的可串行性
若事务是顺序执行的,则称这种执行方式为串行执行。
若多个事务可以同时被数据库管理系统接受,在时间上重叠执行,则称这种执行方式为并发执行。
多个事务的并发执行是正确的,当且仅当其结果与按某一顺序的串行执行的结果相同,则我们称这种调度为可串行化的调度。
可串行性是并发事务正确性的准则。
设有两个事务,分别包含下列操作:
事务T1:读B;A=B+1;写回A
事务T2:读A;B=A+1;写回B
设A、B的初值均为4,给出对这两个事务不同的调度策略。
策略1:串行调度
策略2:并发调度
7.7 两段锁协议
两段锁协议是指所有的事务分为两个阶段:
申请封锁期(也称扩展阶段):申请要进行的封锁。
释放封锁期(也称收缩阶段):释放所占有的封锁。
在申请期不允许释放任何锁,在释放期不允许申请任何锁,这就是两段式封锁。
--事务1的封锁序列
Slock A ... Slock B ... Xlock C …Unlock B ... Unlock A ... Unlock C;
--事务2的封锁序列
Slock A ...Unlock A ...Slock B ...Xlock C...
Unlock C …Unlock B;
若并发执行的所有事务都遵守两段锁协议,则这些事务的任何并发调度策略都是可串行化的。
若并发事务的一个调度是可串行化的,不一定所有事务都符合两段锁协议。
所有遵守两段锁协议的事务,其并行执行的结果一定是正确的。
两段锁协议与防止死锁的一次封锁法:
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议
但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁
遵守两段锁协议的事务发生死锁:
两段锁协议与三级封锁协议:
两类不同目的的协议
两段锁协议:保证并发调度的正确性
三级封锁协议:在不同程度上保证数据一致性
遵守第三级封锁协议必然遵守两段锁协议
7.8 数据库故障类型
数据恢复:数据库中的数据无法保证绝对不遭受破坏,因此系统必须具有检测故障并把数据从错误状态恢复到某一正确状态的功能,这就是数据库的恢复。
数据库故障类型:事务内部故障、系统故障、介质故障、计算机病毒
7.8.1 事务故障
事务故障又可分为两种:
① 可以预期的事务故障:即在程序中可以预先估计到的错误 可以在事务的代码中加入判断和ROLLBACK语句。当事务执行到ROLLBACK语句时,由系统对事务进行回退操作,即执行UNDO(撤消)操作。
② 非预期的事务故障:即在程序中发生未估计到的错误 事务没有达到预期的终点(提交或回滚),数据库可能处于不正确状态。 此时由系统直接对该事务执行UNDO处理。
银行转帐事务,把一笔金额从帐户A转移到帐户B,如果帐户A的存款不够转帐的金额,该事务就必须撤消。
BEGIN TRANSACTION SELECT BALANCE FROM ACCOUNTS WHERE ACCOUNT_NO = A BALANCE_A = BALANCE (读取帐户A的余额BALANCE_A) BALANCE_A = BALANCE_A - AMOUNT;(AMOUNT为转帐金额) IF ( BALANCE_A<0 ) THEN { 打印‘金额不足,不能转帐’; ROLLBACK;(撤销该事务)} ELSE ……
7.8.2 系统故障、介质故障、计算机病毒
1.系统故障:指造成系统停止运转、系统要重启的故障。例如,特定硬件错误(CPU故障)、操作系统故障、突然停电等。 影响所有正在运行的事务,但不破坏数据库。
3.介质故障:将破坏数据库,并影响正在存取这部分数据的所有事务。
4.计算机病毒:破坏性很大,极易传播。
7.9 数据转储与日志
7.9.1 数据库恢复基本原理与技术
基本原理就是“冗余”,即数据库重复存储。
恢复机制涉及到两个关键问题: 如何建立冗余数据;如何利用这些冗余数据实施数据库恢复
建立冗余数据最常用的技术是:
数据转储:DBA定期地将整个数据库复制到磁带或另一个磁盘上保存起来的过程。这些备用的数据文本称为后备副本或后援副本。
转储方法:静态转储与动态转储;海量转储与增量转储
登录日志文件:日志文件是用来记录事务对数据库的更新操作的文件。
数据转储是数据库恢复中采用的基本技术。
通常在一个数据库系统中,两种方法是一起使用的。
7.9.2 静态转储
在系统中无运行事务时进行转储
转储开始时数据库处于一致性状态
转储期间不允许对数据库的任何存取、修改活动
优点:实现简单
缺点:降低了数据库的可用性、转储必须等用户事务结束、新的事务必须等转储结束
7.9.3 动态转储
利用动态转储得到的副本进行故障恢复
需要把动态转储期间各事务对数据库的修改活动登记下来,建立日志文件
后备副本加上日志文件才能把数据库恢复到某一时刻的正确状态
7.9.4 海量转储与增量转储
海量转储: 每次转储全部数据库
增量转储: 只转储上次转储后更新过的数据
海量转储与增量转储比较:
从恢复角度看,使用海量转储得到的后备副本进行恢复往往更方便
但如果数据库很大,事务处理又十分频繁,则增量转储方式更实用更有效
7.9.5 转储策略
应定期进行数据转储,制作后备副本。
但转储又是十分耗费时间和资源的,不能频繁进行。
DBA应该根据数据库使用情况确定适当的转储周期和转储方法。
例: 每天晚上进行动态增量转储;每周进行一次动态海量转储;每月进行一次静态海量转储。
7.9.6 登记日志文件(Logging)
日志文件是用来记录事务对数据库的更新操作的文件。
日志文件主要有两种格式: 以记录为单位的日志文件;以数据块为单位的日志文件
日志记录(log record)的内容:
日志文件的用途
日志文件的用途:①事务故障的恢复 ②系统故障的恢复 ③介质故障的恢复
具体作用:
(1) 事务故障的恢复和系统故障的恢复必须使用日志文件
(2) 在动态转储方式中必须建立日志文件,后援副本和日志文件结合才能有效地恢复数据库
(3) 在静态转储方式中也可以建立日志文件
7.10 数据恢复策略
7.10.1 事务故障的恢复
事务故障:事务在运行至正常终止点前被中止
恢复方法:由恢复子系统应利用日志文件撤消(UNDO)此事务已对数据库进行的修改
事务故障的恢复由系统自动完成,不需要用户干预
事务故障的恢复步骤:
①反向扫描文件日志(即从最后向前扫描日志文件),查找该事务的更新操作。
②对该事务的更新操作执行逆操作。即将日志记录中“更新前的值”(Befor Image, BI)写入数据库。
(插入---删除; 删除---插入; 修改---修改前的值取代修改后的值) 即将日志记录中“更新前的旧值”写入DB。
③继续反向扫描日志文件,查找该事务的其他更新操作,并做同样处理。
④如此处理下去,直至读到此事务的开始标记,事务故障恢复就完成了。
7.10.2 系统故障的恢复
系统故障造成数据库不一致状态的原因:
一些未完成事务对数据库的更新已写入数据库
一些已提交事务对数据库的更新还留在缓冲区没来得及写入数据库
恢复方法
1.Undo 故障发生时未完成的事务
2.Redo 已完成的事务
系统故障的恢复由系统在重新启动时自动完成,不需要用户干预
系统故障的恢复步骤:
1. 正向扫描日志文件(即从头扫描日志文件)
Redo队列: 在故障发生前已经提交的事务
T1, T3, T8…..
Undo队列:故障发生时尚未完成的事务
T2, T4, T5, T6, T7, T9 …...
2. 对Undo队列事务进行UNDO处理
反向扫描日志文件,对每个UNDO事务的更新操作执行逆操作
T2, T4, T5, T6, T7, T9 ……
3. 对Redo队列事务进行REDO处理
正向扫描日志文件,对每个REDO事务重新 执行登记的操作
T1, T3, T8…..
7.10.3 介质故障的恢复
最严重的故障, 磁盘上的物理数据和日志文件被破坏
恢复方法:重装DB后援副本,重做已完成的事务
介质故障的恢复由DBA重装DB副本和日志文件副本,然后执行系统提供的恢复命令,由系统执行恢复。
恢复步骤:
1. 装入最新的后备数据库副本,使数据库恢复到最近一次转储时的一致性状态。
对于静态转储的数据库副本,装入后数据库即处于一致性状态
对于动态转储的数据库副本,还须同时装入转储时刻的日志文件副本,利用与恢复系统故障相同的方法(即REDO+UNDO),才能将数据库恢复到一致性状态。
2. 装入有关的日志文件副本,重做已完成的事务。
首先扫描日志文件,找出故障发生时已提交的事务的标识,将其记入重做队列。
然后正向扫描日志文件,对重做队列中的所有事务进行重做处理。即将日志记录中“更新后的值”写入数据库。
介质故障的恢复需要DBA介入
DBA的工作:
重装最近转储的数据库副本和有关的各日志文件副本
执行系统提供的恢复命令
具体的恢复操作仍由DBMS完成
7.10.4 登记日志文件的原则
为保证数据库是可恢复的,登记日志文件时必须遵循两条原则:
登记的次序严格按并行事务执行的时间次序
必须先写日志文件,后写数据库
写日志文件操作:把表示这个修改的日志记录写到日志文件
写数据库操作:把对数据的修改写到数据库中
为什么要先写日志文件:
写数据库和写日志文件是两个不同的操作
在这两个操作之间可能发生故障
如果先写了数据库修改,而在日志文件中没有登记下这个修改,则以后就无法恢复这个修改了
如果先写日志,但没有修改数据库,按日志文件恢复时只不过是多执行一次不必要的UNDO操作,并不会影响数据库的正确性