事务是访问并可能更新各种数据项的一个执行单元。我们要求数据库系统维护事务的以下性质
这些性质通常被称为ACID特性。
关于事务的一些概念
事务的状态:
进入中止状态后,系统有两个选择:
事务处理系统允许多个事务并发地执行,并发处理的好处主要是两点:
数据库系统必须控制事务之间的交互,以防止它们破坏数据库的一致性。系统称为并发控制机构。
调度结果等价于串行调度的调度称为可串行化调度。
冲突指令
非冲突指令
如果调度S可以经过一系列的非冲突指令交换转换成S’,我们称S与S’是冲突等价的。
冲突可串行化:若一个调度S与一个串行调度冲突等价,则称S是冲突可串行化
如果图中没有环的则称为可以冲突串行化。
视图可串行化
一个可恢复调度应满足:对每对事务A、B,若A读取了由B所写的数据项,则要保证B先提交,A后提交。
无级联调度:对于每队事务A和B,如果A读取了B之前写的数据,B必须在读操作之前提交。每一个无级联调度也都是可恢复调度
隔离性级别越高越能保证一致性,但响应、性能上也要相应牺牲
上述所有隔离性级别都不允许脏写,即如果一个数据项已经被另外一个尚未提交或中止的事务写入,则不允许对该数据项执行写操作。
确保隔离性的方法之一是要求对数据项以互斥方式进行访问。实现该需求最常用的方法是只允许事务访问当前该事务持有锁(lock)的数据项。
要求每个事务都要根据自己将对数据项Q进行的操作类型申请适当的锁。事务只有在并发控制管理器授予所需锁后才能继续其操作。
这两种锁类型的使用可以让多个事务读取一个数据项但是限制同时只能有一个事务进行写操作。
对于给定的一个锁集合,可在它们上按如下方式定义一个相容的类型。
要访问一个数据项,事务必须首先给该数据项加锁。如果该数据项已经被另一个事务加上不相容的类型的锁,那只好等待。
饿死:指的是当前事务A申请对数据X加写锁,X当前已被事务B加了共享读锁,A等待中持续有一系列对X共享读请求,均被满足,故而A持续等待的现象(事务A可能永远不能取得进展)。
可通过按如下方式授权加锁来避免事务饿死:当事务A申请对数据项X加M型锁时,并发管理器授权加锁的条件是:
因此,一个加锁请求就不会被其后的加锁申请阻塞。
保证可串行性的一个协议是两阶段封锁协议。该协议要求每个事务分两个阶段提出加锁和解锁申请:
前提:需要知道所有需要访问的数据项及其访问顺序。
依据参与并发事务集合产生的数据项集,数据项顺序要求构造一个图,可以视为有向无环图,称为数据库图。
在树形协议中,可用的加锁指令只有 lock-X 。
单个事务对一个数据项最多能加一次锁,并且遵从以下规则:
存在一个事务环路,环路中每个事务因为下一个事务持有的资源而阻塞(每个事务本身持有造成上一事务阻塞的资源)。
死锁产生时,解决是选择一个或若干事务,让其回滚到获得某个锁时刻之前,它在该点得到一个锁,而释放该锁就可以解决死锁。
处理死锁问题的两类方法:
死锁预防
死锁预防有两种方法:
第一:对加锁请求进行排序或要求同时获得所有锁来保证不会发生循环等待。
最简单的机制是:事务开始前一次封锁所有涉及数据项。此外,要么一次全部封锁要么全不封锁。
缺点:
另一种机制:对数据项指定顺序,同时要求事务需按规定顺序加锁数据项。
该方法的一个变种是使用数据项与两阶段封锁关联的全序。
第二:每当有可能死锁时,进行事务回滚。
使用抢占和事务回滚。
事务B持有A的锁,可能将B回滚让其释放锁,来满足A。
给每个事务赋一个唯一的时间戳,系统用时间戳来决定事务应等待还是回滚。
利用时间戳的两种不同的死锁预防机制:
1.wait-die——非抢占。
事物A申请的数据项被事务B持有。
若A的时间戳小于B,则A等待;若A的时间戳大于B,则A回滚。
2.wound-wait——抢占(时间戳越小优先级越高)。
事务A申请的数据项被事务B持有。
若A的时间戳大于B的时间戳,则A等待;若A的时间戳小于B的时间戳,B回滚。
另一种处理死锁的简单方法基于锁超时。
超时时,事务回滚。
超时时间合理确定是一个问题。一般而言很难确定一个事务超时之前应等待多长时间。如已发生死锁,等待时间太长导致不必要的延迟。如果等待时间太短,即便没有死锁,也可能引起事务回滚,造成资源浪费。
该机制也可能会产生饿死。
死锁检测与恢复
如果系统没有采用能保证不产生死锁的协议,那么系统必须采用检测与恢复机制。
检查系统状态的算法周期性地激活,判断有无死锁发生。如果发生死锁,则系统必须试着从死锁中恢复。
系统必须:
从死锁中恢复
当一个检测算法判定存在死锁时,系统必须从死锁中恢复。解除死锁最通常的做法是回滚一个或多个事务。需采取的动作有三个:
1.选择牺牲者
2.回滚
3.饿死
同一事务总是被选为牺牲者时的现象。
可在选择时考虑到事务的已被回滚次数。