并发控制

所谓事务,是指用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。
事务具有四个特性:
1.原子性 2.一致性(Consistency) 3.隔离性(Isolation)持续性(Durability)

并发控制概述

事务是并发控制的基本单位,保证事务ACID特性是事务处理的重要任务。
并发操作带来的数据的不一致性问题:
1.丢失修改
两个事务T1和T2读入同一组数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。
2.不可重复读
不可重复读是指事物T1读取数据后,事务T2执行更新操作,使T1无法再前一次读取的结果。不重复读包括三种结果:
(1)事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值。
(2)事务T1按一定条件从数据库中读取某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录神秘消失了。
(3)事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按照相同条件读取数据时,发现多了一些记录。
后两种不可重复读有时也称为幻影现象。
3.读脏数据(Dirty Read)
读脏数据是指事物T1修改某一数据,并将其写会磁盘,事物T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据被恢复原值,T2读到的数据与数据库中的数据不一致,则T2读到的数据就为“脏数据”。
产生上述三类数据不一致的主要原因是并发操作破坏了事务的隔离性。
并发控制的主要技术有封锁(Locking)、时间戳(Timestamp)和乐观控制法,商用的DBMS一般都采用封锁方法。

封锁

所谓封锁就是事务T在对某个数据对象例如表、记录等操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该数据对象有了一定的控制,在事务T释放它的锁之前,其他的事务不能更新此数据对象。
基本的封锁类型有两种:
1.排它锁(Exclusive Locks),简称X锁
2.共享锁(Share Locks),简称S锁
排它锁又称写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。
共享锁又称读锁。若事务T对数据对象A加上S锁,则事务T可以读A但是不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。

活锁和死锁

活锁

如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的锁后系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求…T2有可能永远等待下去,这就是活锁的情形。
避免活锁采用的方法是先来先服务的策略。

死锁

1.死锁的预防

通常有两种方法:1.一次封锁法 2.顺序封锁法
一次封锁法:要求每个事务必须一次性将所有要使用的数据全部加锁,否则就不能继续执行。
存在问题:1.一次就讲以后要用到的全部数据加锁,扩大了封锁的范围,从而降低了系统的并发度。
2.数据库中的数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难先景区地确定每个事务索要封锁的数据对象。
顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
存在问题:1.数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作不断变化,要维护这样的资源的封锁顺序非常困难,成本很高。
2.事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁那些对象,因此也很难按规定的顺序去施加封锁。
DBMS在解决死锁的问题上普遍采用的是诊断并解除死锁的方法

2.死锁的诊断与解除

1.超时法
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。
不足:1.有可能误判死锁,事务因为其他原因使等待时间超过时限,系统会误认为发生了死锁。
2.时限若设置得太长,死锁发生后不能及时发现。
2.等待图法
事务等待图是一个有向图G=( T, U).T为结点的集合,每个结点表示正在运行的事务,U为边的集合,每条边表示事务的等待情况。
事务等待情况动态反应了所有事务的等待情况。并发控制子系统周期性地生成事务等待图,并进行检测。如果发现图中存在回路,则表示系统中出现了死锁。
DBMS的并发控制子系统一旦检测到系统中存在死锁,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有的锁,使其他事务得以继续运行下去。

并发调度的可串行性

DBMS对并发事务不同的调度可能会产生不同的结果,那么什么样的调度是正确的呢?显然,串行调度是正确的。执行结果等价于串行调度的调度也是正确的。这样的调度叫可串行化调度。

可串行化调度

定义:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行的执行这些事务时的结果相同,称这个调度策略是可串行化的。
可串行性是并发事务正确执行的准则。

冲突可串行化调度

冲突操作是指不同的事务对同一个数据的读写操作和写写操作:
Ri(x)与Wj(x) //事务Ti读x,Tj写x
Wi(x)与Wj(x) //事务Ti写x,Tj写x
其他操作是不冲突操作。
一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度Sc’,如果Sc’是串行的,称调度Sc为冲突可串行化的调度。一个调度是冲突可串行化,一定是可串行化的调度。
冲突可串行化调度是可串行化调度的充分条件,不是必要条件。还有不满足冲突可串行化条件的可串行化调度。

两段锁协议

在运用封锁方法时,对数据对象加锁时需要约定一些规则,例如何时申请封锁、持所时间、何时释放封锁等。这些规则被称为封锁协议。
所谓两段锁协议是指所有事务必须分两个阶段对数据项加锁和解锁:
1.在对任何数据进行读、写操作之前,首先要申请并获得对该数据的加锁
2.在释放一个封锁之后,事务不再申请和获得任何其他封锁
两段锁的含义是,事务分为两个阶段,第一个阶段是获得封锁,也称为扩展阶段。在这阶段,事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁。第二个阶段是释放封锁,也称为收缩阶段。在这阶段,事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁。
事务遵守两段锁协议是可串行化的充分条件,而不是必要条件。
要注意两段锁协议和防止死锁的一次封锁法的异同之处。一次封锁法要求每个事物必须一次将所有要使用的数据全部加锁,否则就不能继续执行。因此一次封锁遵守两段锁协议;但是两段锁协议并不要求事务必须将所有要是有的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。

封锁的粒度

封锁对象的大小称为封锁粒度。封锁对象可以是逻辑单元,也可以是物理单元。以关系数据库为例,封锁对象可以是这样一些逻辑单元:属性值、属性值的集合、元组、关系、索引项、整个索引直至整个数据库;也可以是这样一些物理单元:页、物理记录等。
如果在一个系统中同时支持多种封锁粒度供不同的事务选择是比较理想的,这种封锁方法称为多粒度封锁。

多粒度封锁

多粒度树:多粒度树的根节点是整个数据库,表示最大的数据粒度。叶节点表示最小的数据粒度。
多粒度封锁协议允许多粒度树种的每个节点被独立地加锁。对一个结点加锁意味着这个结点的所有后裔结点也被加以同样类型的锁。因此,在多粒度封锁中一个数据对可能一两种方式加锁,显示封锁和隐式封锁。
显示封锁是应事务的要求直接加到数据对象上的封锁;
隐式封锁是该数据对象没有独立加锁,是由于其上级结点加锁而使该数据对象加上了锁。
一般地,对某个数据对象加锁,系统要检查该数据对象是哪个有误显示封锁与之冲突;还要检查其所有上级结点,看本事务的显示封锁是否与该数据对象上的隐式封锁冲突;还要检查所有下级结点,看上面的显示封锁是否与本事务的隐式封锁冲突。

意向锁

意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对他的上层结点加意向锁。
意向共享锁(IS锁)、意向排它锁(IX锁)、共享意向排它锁(SIX锁)
IS锁
如果对一个数据对象加S锁,表示它的后裔结点拟(意向)加S锁。
IX锁
如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。
SIX锁
如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX= S+IX。
具有意向锁的多粒度封锁方法中任一事务T要对一个数据对象加锁,必须先对它的上层结点加意向锁。申请封锁时应该按自上而下的次序进行;释放封锁时应该按自下而上的顺序进行。

基于时间戳的协议

另一种决定事务可串行化次序的方法是事先选定事务的次序。其中最常用的方法是时间戳排序机制。

时间戳

对于系统中每个事务Ti,我们把一个唯一的固定时间戳和它联系起来,此时间戳记为TS(Ti)。该时间戳是在事务Ti开始执行前由数据库系统赋予的。若事务Ti已赋予时间戳TS(Ti),此时有一种新事务Ti进入系统,则TS(Ti) < TS(Tj)。实现这种机制可以采用下面这两个简单的办法:
1.使用系统时钟的值作为时间戳:即事务的时间戳等于该事务进入系统时的时钟值。
2.使用逻辑计数器,每赋予一个时间戳,计数器增加计数,即事务的时间戳等于该事务进入系统时的计数器值。
要实现这个机制,每个数据项Q需要与两个时间戳值相关联:
W-timestamp(Q)表示成功执行write(Q)的所有事务的最大时间戳
R-timestamp(Q)表示成功执行read(Q)的所有事务的最大时间戳。

时间戳排序协议

保证任何有冲突的read或write操作按时间戳顺序执行。该协议运作方式如下:
1.假设事务Ti发出read(Q)
a.若TS(Ti) < W-timestamp(Q),则Ti需要读入的Q值已经被覆盖。因此,read操作被拒绝。Ti回滚。
b.若TS(Ti)>=W-timestamp(Q),则执行read操作,R-timestamp(Q)被设置为R-timestamp(Q)与TS(Ti)两者的最大值。
2.假设事务Ti发出write(Q)
a.若TS(Ti) < R-timestamp(Q),则Ti产生的Q值是先前所需要的值,且系统已假定该值不会再产生。因此,write操作被拒绝,Ti回滚。
b.若TS(Ti) < W-timestamp(Q),则Ti时图写入的Q值已经过时。因此,write操作被拒绝,Ti回滚。
c.其他情况,系统执行write操作,将W-timestamp(Q)设置为TS(Ti)。
如果事务Ti由于发出read或write操作而被并发控制机制回滚,则系统赋予它新的时间戳并重新启动。
时间戳排序协议保证冲突可串行化,这因为冲突操作按时间戳顺序进行处理。
该协议保证无死锁,因为不存在等待的事务。但是,当一系列冲突的段事务引起长事务反复重启时,可能导致肠事务饿死的现象。如果发现一个事务反复重启,与之冲突的事务应当暂时阻塞,以使该事务能够完成。
该协议可能产生不可恢复的调度。然而,该协议可以进行扩展,用以下几种方法之一来保证调度可恢复:
1.在事务末端执行所有的写操作能保证可恢复性和无级联性,这些写操作必须具有下述意义的原子性:在写操作正在执行的过程中,任何事物都不允许访问已写完的任何数据项。
2.可恢复性和无级联性也可以通过使用一个受限的发封锁形式来保证,由此,对未提交数据项的读操作被推迟到更行该数据项的事务提交之后。
3.可恢复性可以通过跟踪为提交写操作来单独包装,一个事物Ti读取了其他事物所写的数据,只有在其他事物都提交之后,Ti才能提交。

基于有效性检查的协议

有效性检查协议要求每个事物Ti在其生命周期中按两个或三个阶段执行,则取决于事务是一个只读事务还是一个更新事务。
1.读阶段:在这一阶段中,系统执行事务Ti。个数据项值被读入并保存在事务Ti的局部变量中。所有write操作都是对局部临时变量进行的,并不对数据库进行真正的更新。
2.有效性检查阶段:对事务Ti进行有效性测试,判断是否可以执行write操作而不违反可串行性。如果事务有效性测试失败,则系统终止这个事务。
3.写阶段:若事务Ti已通过有效性检查,则保存Ti任何写操作结果的临时就被变量值被复制到数据库中。只读事务忽略这个阶段。
为了进行有效性检测,我们需要知道事务Ti的各个阶段何时进行。为此,我们将三个不同的时间戳与事务Ti相关联。
1.Start(Ti):事务Ti开始执行的时间
2.Validation(Ti):事务Ti完成读阶段并开始其有效性检查的时间
3.Finish(Ti):事务Ti完成写阶段的时间
我们利用时间戳Validation(Ti)的值,通过时间戳排序技术决定可串行性排序。因此,值TS(Ti) = Validation(Ti)。并且若TS(Tj) < TS(Tk),则产生的任何调度必须等价于事务Tj出现在Tk之前的某个串行调度。选择Validation(Ti)而不是Start(Ti)作为事务Ti的时间粗是因为在冲突频度很低的情况下期望有更快的响应时间。
事务Ti的有效性测试要求任何满足TS(Tk) < TS(Tj),的事务Tj必须满足下面两条件之一:
1.Finish(Tk) < Start(Tj)。因为Tk在Tj开始之前完成其执行,所以可串行性次序得到了保证
2.Tk缩写的数据项集与Tj所读数据项集不相交,并且Tk的写阶段在Tj开始其有效性检查阶段之前完成。这个条件保证Tk与Tj的写不重叠。因为Tk的写不影响Tj的读,又因为Tj不可能影响Tk的读,从而保证了可串行性次序。
在有效性检查机制中,由于事务乐观的执行,假定他们能够完成执行并且最终有效,因此也称为乐观的并发控制机制。与之相反,封锁和时间戳排序是悲观的,因为当他们检测到一个冲突时,它们强迫事务等待或回滚,即使该调度有可能是冲突可串行的。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值