数据库并发控制

来源:互联网 发布:淘宝如何上传3c认证 编辑:程序博客网 时间:2024/06/15 07:11

数据库并发控制
综述:事务可以一个一个的串行执行,即每个时刻只有一个事务运行,其他事务必须等到这个事务运行结束之后才能运行。事务在执行时需要不同的资源,cpu资源,I/O,有时需要通信。如果事务串行执行,则会导致很多资源闲置,为了充分利用资源,应该允许事务并行执行。在单处理机系统中,事务的并行执行实际上是事务的交叉运行,即同一时刻系统只有一个事务在运行。

并发操作带来的问题

1.丢失数据:两个事务T1和T2读入同一个数据进行修改,T2提交的修改会覆盖T1对数据的修改,导致T1对数据的修改丢失,如定飞机票的例子,
2.不可重复读:事务T1读取数据后,如果T2修改了数据,那么事务T1无法再现前一次读取的结果;包括三种情况:@1:事务T1读取某一数据后,T2对其进行了修改,当T1再次读取该数据时,得到与前一次不一样的值。@2:事务T1读取某一数据后,T2对其进行了删除操作,当T1再次按相同的条件读取时,发现数据记录消失了。@3:T1读取某一数据后,T2进行了插入操作,当T1再次按相同条件读取时,发现多了一些记录。后两种不可重复读现象有时成”幻影”现象
3.读脏数据:T1修改某一数据并写会磁盘,T2读取同一数据后,T1由于某种原因被撤销(rollback),这时T1修改的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就是脏数据,即不正确的数据。
并发控制机制就是要用正确的方式调度并发操作,使一个事务的执行不受其他事务的干扰。并发控制的主要技术有封锁,时间戳和多版本控制法等

封锁

所谓封锁就是事务T在对某个数据对象例如表,记录等操作之前,先向系统发出请求,对其加锁。加锁后T就对该数据对象拥有了一定的控制,在T释放它的锁之前,其他事务不能更新此数据。
基本的封锁类型有两种:排他锁(简称X锁)和共享锁(简称S锁)

排他锁(写锁)

T对数据对象A加上X锁,则只允许T对A进行更新操作,任何其他事务都不能对A加任何类型的锁,直到T释放了A上的锁为止,保证了在T操作A时其他事务都不能再读取或修改A的值。

共享锁(读锁)

T对数据对象A加上S锁,则T可以读A但不能修改A,其他事务只能对A加S锁不能加X锁,直到T释放S锁为止。保证了其他事务可以读A但是不能修改A。

封锁协议

除了X锁和S锁这两种基本的锁,还需要约定一些规则。例如,何时申请X锁或S锁,持锁时间,何时释放等,这些规则称为封锁协议。下面介绍三级封锁协议,分别在不同程度上解决并发带来的问题。

1.一级封锁协议

一级封锁协议指,T在修改数据R之前必须先对其加X锁,直到事务T结束时才能释放,在释放X锁之前,其他事务可以读取该数据。事物结束包括正常结束(commit)和非正常结束(rollback)。一级封锁协议可防止丢失修改。但是如果仅仅是读数据而不对其进行修改,是不需要加锁的,所以不能保证可重复读(第二次再读该数据,会发生改变)和不读脏数据。

2.二级封锁协议

二级封锁协议指,在一级封锁协议基础之上增加T在读取数据R之前必须先对其请求加S锁,如果在请求加S锁时该数据已经被其他锁锁住,则该请求等待。读完后即可释放S锁。所以他不能保证可重复读(再次读取时数据可能会变),但是能保证不读脏数据。

3.三级封锁协议

三级封锁协议指,在一级封锁协议基础之上增加T在读取数据R之前必须先对其请求加S锁,如果在请求加S锁时该数据已经被其他锁锁住,则该请求等待。直到事务结束释放S锁。所以当事务T为验算再读该数据时,读出的值不变。因此保证了可重复读。
上述三级封锁协议的区别是什么操作需要申请封锁,以及何时释放锁。

活锁和死锁

和操作系统一样,封锁的方法可能引起活锁和死锁等问题。

活锁

活锁的情形是事务T有可能对数据R的请求永远处于等待,避免活锁的简单方法是采用先到先服务的方法。当多个事务请求封锁同一个数据对象时,按请求封锁的先后顺序对事务进行排队,当该对象的锁被释放时就批准请求队列的第一个事务获得锁。

死锁

T1在等待T2,同时T2在等待T1,T1和T2两个事务永远都不会结束,这就是死锁现象。在数据库中解决死锁的方法主要有两类,一是采用一定措施来预防死锁的发生,二是允许死锁的发生,采用一定手段定期诊断系统中有无死锁,若有则解除之。

1.死锁的预防

数据库中的死锁,产生的原因是两个或多个事务都已经封锁了一些数据对象,同时又都在请求一些已经被封锁的对象,从而出现死等待。防止死锁的发生就是要破坏死锁产生的条件。预防死锁的方法有以下两种
1.一次封锁法:要求每个事务必须一次将所有要请求的数据对象全部加锁,否则不能继续执行。一次封锁法虽然能有效防止死锁,但是也存在很多问题,第一,一次将所有要用到的数据资源全部加锁,势必扩大了封锁的范围,从而降低了系统的并发度。第二,数据库中的数据是不断变化的,有些可能用不到的数据在执行过程中可能会用到,所以很难精确的确定事务要封锁的数据对象,为此只能扩大封锁范围,将事务在执行过程中可能要用到的数据对象全部加锁,进一步降低了并发度。
2.顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按照该封锁顺序实施封锁。例如在B树中,封锁顺序必须是从根节点开始,然后是下一级子节点,逐级封锁。虽然有效但同样存在问题,第一,数据库系统中要封锁的对象极多,而且随着数据库的增,删,改等操作而不断变化,因此要维护资源的封锁顺序非常困难,成本很高。第二,事务的封锁请求随着事务的执行而动态变化,很难事先确定每一个事务要封锁哪些对象,因此很难按规定的顺序去封锁数据对象。
可见操作系统中广为采用的解决死锁的方法,在数据库中并不太合适,不符合数据库的特点,因此数据库管理系统在解决死锁的问题上采用诊断并解除死锁的办法。

2.死锁的诊断与解除

数据库系统中诊断死锁的方法与操作系统中类似,一般使用超时法或等待事物图法。
1.超时法:如果一个事务等待的时间超过了规定的时限,就认为发生了死锁。超时法实现简单,但其不足也很明显,一是有可能误判死锁,如事务因其他原因而使等待时间超过时限,系统会误认为发生了死锁;二是时限若设置的太长,死锁发生后不能及时发现。
2.等待图法:事务等待图是一个有向图,若T1等待T2,则在T1,T2之间划一条有向边,从T1指向T2。事务等待图动态反应了所有事务的等待情况,并发控制子系统周期性地(比如每隔数秒)生成事务等待图,并进行检测。如果发现图中存在回路,则表示系统中出现了死锁。死锁的情况很多,有可能回路中还有回路。当数据库管理系统的并发子系统一旦检测到系统存在死锁,就要设法解除,通常采用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有锁,其他事务得以继续运行下去。当然撤销事务所执行的所有操作必须加以恢复。

并发调度的可串行性

数据库管理系统对并发事务不同的调度可能会产生不同的结果,那么什么样的调度是正确的呢?显然,串行调度是正确的,执行结果等价于串行调度的调度也是正确的。这样的调度叫做可串行调度。
定义:多个事务的并发调度是正确的,当且仅当其结果与按某一次序串行的执行这些事务时的结果相同,称这种调度是可串行化调度。
可串行性是并发事务正确调度的准则,按这个准则规定,一个给定的并发调度,当且仅当它是可串行化的,才认为是正确调度。
冲突可串行化调度
冲突操作是指不同的事务对同一个数据的读写操作和写写操作。
Ri(x)与Wj(x) /*事务Ti读x,Tj写x,其中i!=j
Wi(x)与Wj(x) /*事务Ti写x,Tj写x,其中i!=j
其他操作都不是冲突操作。
一个调度Sc在保证冲突操作次序不变的情况下,通过交换两个事务不冲突的操作的次序得到另一个调度Sc1,如果Sc1是串行的,称Sc是冲突可串行化的调度。若一个调度是冲突可串行化,则一定是可串行化调度。

两段锁协议

数据库管理系统普遍采用两段锁(简称2PL)协议的方法实现并发调度的可串行性,从而保证调度的正确性。
所谓两段锁协议是指事务必须分两个阶段对数据项加锁和解锁。
1.在对任何数据进行读,写操作之前,首先要申请并获得对该数据的封锁,
2.在释放一个锁之后,事务不在申请和获得任何其他锁,
所谓两段锁的含义是指,事务分两个阶段,第一阶段是获得封锁,在这个阶段事务可以申请获得任何数据上的任何类型的锁,但是不能释放任何锁;第二阶段是释放封锁,在这个阶段,事务可以释放任何数据上的任何锁,但是不能再申请任何锁。
事务遵循两段锁协议是事务可串行化调度的充分非必要条件,
两段锁协议和防止死锁的一次封锁法是不同的,一次封锁法是每个事务必须一次将所要的数据全部加锁,否则不能继续执行,因此一次封锁法遵循两段锁协议,但是两段锁协议并不要求一次将所要的数据全部加锁,所以两段锁协议可能会导致死锁。

封锁的粒度

封锁对象的大小称为封锁粒度,封锁对象可以是逻辑单元:属性值,属性值的集合,元祖,关系,索引项,整个索引直至整个数据库;也可以是这样一些物理单元:页(数据页或索引页),物理记录等。
封锁粒度与系统的并发度和并发控制开销密切相关。直观地看,封锁粒度越大,数据库所能封锁的数据对象越少,并发度就越小,系统开销就越小;反之,封锁粒度越小,数据库所能封锁的数据对象越多,并发度就越大,系统开销就越大。
系统中应支持多种封锁粒度供不同的事务使用,这种封锁方法称为多粒度封锁。
多粒度封锁树,多粒度的根节点是整个数据库,表示最大的数据粒度,叶节点表示最小的数据粒度。
多粒度封锁协议允许多粒度树中每个节点被独立的加锁,对一个节点加锁意味着他的后裔节点被加以同样类型的锁。
显示加锁指根据事务的要求直接加在数据对象上的锁,隐式加锁是该数据对象没有被独立加锁,而是继承其上级节点的锁。在多粒度封锁方法中,显示加锁和隐式封锁的效果是一样的。

意向锁

意向锁的含义是如果对一个节点加意向锁,则说明该节点的下级节点正在被加锁。
IS锁(意向共享锁):如果对一个数据对象加IS锁,表示他的后裔节点拟(意向)加S锁。eg:事务T1要对R1中某个元祖加S锁,则要首先要对R1和数据库加IS锁。
IX锁:如果对一个数据加IX锁,表示他的后裔节点拟加X锁。例如,事务T1要对关系R1中某个元祖加X锁,则要首先要对R1和数据库加IX锁。
SIX锁:如果对一个数据对象加SIX锁,表示对他先加S锁,再加IX锁即SIX=S+IX。eg:要对某个表加SIX锁,表示该事务要读整个表(所以要对该表加S锁),同时会更新个别元祖(所以要对该表加IX锁)
所谓锁的强度指他对其他锁的排斥程度,一个事务在申请锁时以强锁代替弱锁是安全的,反之不是。
具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销。

悲观锁的优点与不足:

悲观锁并发访问控制实际上是”先取锁再访问”的保守策略,为数据处理的安全提供保证。但是在效率方面明显不足,处理加锁的机制会让数据库产生额外的开销,还会增加死锁的可能性;另外在只读型事务处理中,明显不会产生冲突,也没必要使用锁,加锁会产生额外的开销;还会降低并行性,如果某事务封锁了某行数据,则其他事务必须等到该事务处理完之后才能访问该数据。

乐观控制法(乐观锁):

假定并发事务在处理数据时不会产生冲突,在不加锁的情况下各自处理数据。每个事务在提交之前,先检查一下事务读取某数据之后有没有其他事务修改了该数据。如果有其他事务修改了该数据的话,正在提交的事务将回滚。

并发控制除了封锁技术外还有时间戳方法和多版本并发控制等。

时间戳方法:

每个事务开始时都有一个时间戳,每个数据也具有读时间戳和写时间戳,
数据库中每一个记录,都有一个上一次事务读写的时间戳的标记。例如:
读时间戳 写时间戳
记录 A 10000 10000
记录 B 10000 10000
事务1时间戳10001,事务2时间戳10005。
刚开始,记录A, B的时间戳记录都是10000. 事务1开始后,假定读了A,写了B,则此时:
读时间戳 写时间戳
记录 A 10001 10000
记录 B 10000 10001
如果这时事务1暂时在处理拿到的数据,没有进一步数据库操作,而事务2写了记录A,则:
读时间戳 写时间戳
记录 A 10001 10005
记录 B 10000 10001
记录A的写时间戳被改变了。假定事务1之后尝试写记录A,会发现记录A具有“未来”的时间戳,说明记录A被新的事务修改过了。此时事务1知道自己的操作收到了影响,因此事务1可以终止。

多版本并发控制:

版本指数据库中数据对象的一个快照,记录了数据对象某个时刻的状态。不再单纯的使用行锁进行并发控制,而是把行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,可以提高系统的并发操作程度。eg:有一个数据对象A有两个事务,T1是写事务,T2是读事务。假定先启动T1,后启动T2,按照传统的封锁协议,只有当T1执行完操作并释放锁之后,T2才能读取数据A。实际上T1和T2是串行执行的。如果当T1执行的时候,为A生成一个新的版本(记作A1),那么T2就可以读取A1。只是当T2准备提交时要检查一下T1有没有完成,若T1已经完成,则提交T2,否则等到T1完成时,才能提交T2.这样既可以保证事务的可串行性,又提高了事务的并发度。(T1修改A是发生在内存中,创建的A1也是在内存中,T2读取A也是发生在内存中,)
innodb:是通过为每一行记录添加两个额外的隐藏值来实现mvcc。这两个隐藏值(该值为系统版本号)一个记录数据何时被创建,一个记录何时被删除(该值可能为空)。每开始一个新事务,系统版本号都会递增,事务开始时的系统版本号作为事务的版本号,事务版本号可以用来查询和与行的版本号进行比较。因此可以根据对版本号来达到对数据版本控制的目的。
select:innodb会根据以下两个条件进行查找
1.查找事务版本号大于行的创建版本号的数据行(也就是说,行的创建版本号小于等于事务的版本号),这样可以保证事务读取的行,在事务开始之前已经存在。
2.行的删除版本要么未定义,要么大于事务的版本号。这样可以保证事务读取到的行,在事务读取之前没有删除。
insert:innodb为新插入的每一行保存当前系统版本号作为行创建版本号。
delete:innodb为删除的每一行保留当前系统版本号作为行删除版本号。
update:innodb为插入的一行新纪录保存当前系统版本号,作为行创建版本号,同时把当前系统版本号作为原来行的删除版本号。
保存两个额外系统版本号,使得大部分操作都不需要加锁,提高了系统性能,同时保证操作的准确性。不足之处是每行记录都需要额外的存储空间,需要更多的行检查工作。通过行的版本号来减少锁的征用,总体上还是提高了系统性能。

原创粉丝点击