乐观机制的并发控制

来源:互联网 发布:成品app直播源码 编辑:程序博客网 时间:2024/05/01 22:44

最近在学习《On Optimistic Methods for Concurrency》这篇论文的时候对论文进行了一些整理:

论文提出的目的:

1.由于计算机的主存有限,所以无法一次将要处理的数据全部加载入内存,只有在需要使用到该数据的时候才会从辅存中将数据调入主存中。

2.主存中的数据会被多个进程访问

如果不支持并发的话,使得硬件使用率降低。


在并发控制中主要采取的控制方法有两种,即悲观的与乐观的并发控制方式。

所谓悲观的控制方式即采用加锁的形式,来支持数据库的并发访问。

所谓乐观的控制方式主要分为时间戳和有效验证这两种形式。而这篇论文主要是介绍有效验证的这一种形式。


论文中认为悲观的控制方法存在以下问题:

1.维护无死锁状态成为一种系统开销
2.没有一种普遍适用的无死锁协议来支持并发访问
3.常访问节点被锁住(比如树的根节点)
4.事务自身发生错误而中止,而锁只能在事务结束后才被释放


所以提出了乐观的并发控制方法,论文的主题是:假设系统中大部分是只读事务,写事务以及其他极端情况是非常罕见的,为极端情况提出了解决方法,使得系统在大部分情况下能够良好运行,在极端情况下也能够运行。

只读事务下:由于只读事务不会对数据库的一致性产生影响,所以可以对只读事务完全不加限制。

写事务:写事务需要分为三个阶段

1 读。在第一阶段,事务从数据库中读其读集合中的所有元素。事务还在其局部地址空间中计算它将要写的所有值。
2 有效性确认。在第二阶段,调度器通过比较该事务与其他事务的读写集合来确认该事务的有效性
3 写。在第三阶段,事务往数据库中写入其写集合中元素的值。

将写事务分为如下操作:

create create a new object and return its name.
delete( n) delete object n.
read(n, i) read item i of object n and return its value.
write (n, i, u) write u as item i of object n.

COPY( n) create a new object that is a copy of object

n and return its name.
exchange(n1, n2) exchange the names of objects nl and n2.

每个操作的具体执行过程如下:

tcreate = (
n := create;
create set := create set U {IL} ;
return n)
twrite(n, i, u) = (
if n E create set
then write(n, i, u)
else if n E write set
then write( copies[ n], i, u)
else (
m := copy(n);
copies[ n] := m;
write set := write set U {n);
write (copies[n], i, u)))
tread(n, i) = (
read set := read set U {n} ;
if n E write set
then return read (copies[ n], i)

else

return read (n, i))

tdelete (n) = (
delete set := delete set U (n}).

我们可以看到以上想表达的主要思想是:将对数据产生写的事务全放到副本中进行操作,只有当事务通过验证阶段,证明可以执行后,才真正升级为全局性的,同步到数据库中。

1.当写操作成功是,执行for n E write set do exchange ( n, copies[ a]).

2.当写操作发生冲突的时候,执行(for n E delete set do delete(n);
for n E write set do delete( copies[n])).

验证阶段需要满足的3个条件:

我们假设I事务在j事务之前,也就是说I的事务号比j小,

t(i)<t(j)

(1) T(i)必须在T(j)开始读阶段前完成其写阶段

(2) Ti的写集合与Tj的读集合没有交集,并且Ti在Tj开始它的写阶段前完成其写阶段

(3) Ti的写集合与Tj的读或者写集合没有交集,并且Ti在Tj完成它的读阶段前完成其读阶段

也就是说Ti不影响Tj的读或者写阶段


那么我们该在什么时候给事务加上事务号呢?很自然我们会想到在读阶段开始之前就为事务加上事务号,但这并不是最好的选择,因为我们假设i事务比j事务先执行,那么自然的t(i)<t(j),但是如果i的读阶段非常的长,而j快速的完成了读阶段(我们说在读阶段可以不加任何限制)那么j即使完成了读阶段,也只能等待i完成验证阶段后才进行验证,这与我们提高事务的响应时间的初衷是违背的,所以我们可以在事务进行完读阶段后再为事务加上事务号,以此来解决这一问题。


那么当事务失败了该怎么办?答案是,取消事务,让事务重做。因为我们假设这样的情况是极少发生的。

那么如果重做后依然失败呢?那么事务会进入一个临界的阶段,当事务调度器发现了这个饥饿的事务后(通过每个事务的开始时间),就会通过不释放这个事务的信号量(相当于一种锁机制)来使得这个事务成功执行。


验证阶段需要维护的集合:

为了能够验证事务的有效性,调度器需要维护三个集合

1BEGIN,已经开始但尚未完成有效性确认的集合。对这个集合中的每个事务T,调度器维护BEGIN(T),即事务T开始的时间。
2VAL,已经确认有效性但尚未完成第3阶段的事务。对这个集合中的每个事务T,调度器维护BEGIN(T)和VAL(T),即T确认的时间。
3END,已经完成第3阶段的事务。对这样的的事务T,调度器记录记录BEGIN(T)、VAL(T)和END(T),即T完成的时间。已经完成了的事务没有再记录的必要,因此可以周期性的清理END集合。
串行验证:
tbegin = (
create set := empty;
read set := empty;
write set := empty;
delete set := empty;
start tn := tnc)
tend = (
(finish tn := tnc;
valid := true;
for t from start tn + 1 to finish tn do
if (write set of transaction with transaction number t intersects read set)
then valid := false;
if valid
then ((write phase); tnc := tnc + 1; tn := tnc));
if valid
then ( cleanup )
else (backup)).
也就是说通过检查正在进行的事务间,它们的写集合有没有交集来确定验证是否会成功,数据库的一致性是否会受到影响。
我们需要考虑两种情况:

1.假设U事务满足:

a)U在VAL或FIN中;即U已经过有效性确认。

b)FIN(U)>START(T);即U在T开始前没有完成。

c)RS(T)与WS(U)非空,假设其包含数据库元素X。

那么U有可能在T读X后写X。事实上,U甚至可能还没开始写X。由于我们不知道T是否读到U的值,我们必须回滚以避免T和U的动作与假设串行顺序不一致的风险。


2.假设U事务满足:

a)U在UAL中;即U有效性已经成功确认

b)FIN(U)>VAL(T);即U在T进入其有效性确认阶段以前没有完成

c)T和U的写集有交集,假设交集元素为X

 

T和U都必须写X的值,而如果我们确认T的有效性,它就可能在U前写X,由于我们不能确定,我们回滚T以保证它不会违反假设的T在U后的串行顺序。


所以总结起来,验证的规则就是:

对于所有已经过有效性确认且在T开始前没有完成的U,即对于满足FIN(U)>START(T)的U,检测T的读与U的写是否有交集

对于所有已经过有效性确认且在T有效性确认没有完成的U,即对于满足FIN(U)>VAL(T)的U,检测T的写与U的写是否有交集

以上是对单CPU情况下的验证情况,然而对于多CPU情况下,应该有所变动,即:
tend := (
mid tn := tnc;
valid := true;
for t from start tn + 1 to mid tn do
if (write set of transaction with transaction number t intersects read set)
then valid := false;
(finish tn := tnc;
for t from mid tn + 1 to finish tn do
if (write set of transaction with transaction number t intersects read set)
then valid := false;
if valid
then (( writephase); tnc := tnc + 1; tn := tnc));
if valid
then (cleanup)
else (backup)).

并发的验证机制:
tend = (
(finish tn := tnc;
finish active := (make a copy of active);
active := active U { id of this transaction } ) ;
valid := true;
for t from start tn + 1 to finish tn do
if (write set of transaction with transaction number t intersects read set)
then valid := false;
for i E finish active do
if (write set of transaction Ti intersects read set or write set)
then valid := false;
if valid
then (
(write phase);
(tnc := tnc + 1;
tn := tnc;
active := active- (id of this transaction) ) ;
(cleanup))
else (
(active := active- { id of transaction} ) ;
(backup))).
我们可以看到,并发的情况总的来说既是添加了一个active集,让事务并发的进行验证,如果成功则通过,如果失败,则将这个事务从活动集中撤销,让事务重做。


0 0
原创粉丝点击