达梦数据库并发控制技术

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

达梦数据库并发控制技术
数据库是一个共享资源,可以提供多个用户使用。

这些用户程序可以一个一个地串行执行,每个时刻只有一个用户程序运行,执行对数据库的存取,其他用户程序必须等到这个用户程序结束以后方能对数据库存取。

但是如果一个用户程序涉及大量数据的输入/输出交换,则数据库系统的大部分时间处于闲置状态。

因此,为了充分利用数据库资源,发挥数据库共享资源的特点,应该允许多个用户并行地存取数据库。

但这样就会产生多个用户程序并发存取同一数据的情况,若对并发操作不加控制就可能会存取和存储不正确的数据,破坏数据库的一致性,所以数据库管理系统必须提供并发控制机制。

并发控制机制的好坏是衡量一个数据库管理系统性能的重要标志之一。

DM用封锁机制来解决并发问题。

它可以保证任何时候都可以有多个正在运行的用户程序,但是所有用户程序都在彼此完全隔离的环境中运行。

一、并发控制的预备知识
(一)并发控制概述
并发控制是以事务(transaction)为单位进行的。

1.并发控制的单位――事务
事务是数据库的逻辑工作单位,它是用户定义的一组操作序列。

一个事务可以是一组SQL语句、一条SQL语句或整个程序。

事务的开始和结束都可以由用户显示的控制,如果用户没有显式地定义事务,则由数据库系统按缺省规定自动划分事务。

事务应该具有4种属性:原子性、一致性、隔离性和持久性。

(1)原子性
事务的原子性保证事务包含的一组更新操作是原子不可分的,也就是说这些操作是一个整体,对数据库而言全做或者全不做,不能部分的完成。

这一性质即使在系统崩溃之后仍能得到保证,在系统崩溃之后将进行数据库恢复,用来恢复和撤销系统崩溃处于活动状态的事务对数据库的影响,从而保证事务的原子性。

系统对磁盘上的任何实际数据的修改之前都会将修改操作信息本身的信息记录到磁盘上。

当发生崩溃时,系统能根据这些操作记录当时该事务处于何种状态,以此确定是撤销该事务所做出的所有修改操作,还是将修改的操作重新执行。

(2)一致性
一致性要求事务执行完成后,将数据库从一个一致状态转变到另一个一致状态。

它是一种以一致性规则为基础的逻辑属性,例如在转账的操作中,各账户金额必须平衡,这一条规则对于程序员而言是一个强制的规定,由此可见,一致性与原子性是密切相关的。

事务的一致性属性要求事务在并发执行的情况下事务的一致性仍然满足。

它在逻辑上不是独立的,它由事务的隔离性来表示。

(3)隔离性
隔离性意味着一个事务的执行不能被其他事务干扰。

即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

它要求即使有多个事务并发执行,看上去每个成功事务按串行调度执行一样。

这一性质的另一种称法为可串行性,也就是说系统允许的任何交错操作调度等价于一个串行调度。

串行调度的意思是每次调
度一个事务,在一个事务的所有操作没有结束之前,另外的事务操作不能开始。

由于性能原因,我们需要进行交错操作的调度,但我们也希望这些交错操作的调度的效果和某一个串行调度是一致的。

DM实现该机制是通过对事务的数据访问对象加适当的锁,从而排斥其他的事务对同一数据库对象的并发操作。

(4)持久性
系统提供的持久性保证要求一旦事务提交,那么对数据库所做的修改将是持久的,无论发生何种机器和系统故障都不应该对其有任何影响。

例如,自动柜员机( ATM)在向客户支付一笔钱时,就不用担心丢失客户的取款记录。

事务的持久性保证事务对数据库的影响是持久的,即使系统崩溃。

正如在讲原子性时所提到的那样,系统通过做记录来提供这一保证。

DM没有提供显式定义事务开始的语句,第一个可执行的SQL语句(除CONNECT语句外)隐含事务的开始,但事务的结束可以由用户显式的控制。

在DM中以下几种情况都结束 (正常,非正常)某一事务:
(1)当某一连接的属性设置为自动提交,每执行一条语句都会提交;
(2)遇到COMMIT/ROLLBACK语句,便提交/回滚一事务;
(3)当系统的 DDL自动提交开关打开时(缺省为打开),遇到DDL语句则自动提交该DDL语句和以前的DML和DDL操作;
(4)事务所在的程序正常结束和用户退出;
(5)系统非正常终止时;
说明:DM在配置文件中提供了DDL语句的自动提交开关DDL_AUTO_COMMIT。

当此配置项的值为 1(缺省情况)时,所有DDL语句自动提交;当此配置项的值为0时,除CREATEDATABASE、ALTERDATABASE和CREATESCHEMA语句外的所有DDL语句都不自动提交。

DM中的一致性是以事务为基础的。

DM通过提交和回滚分别用于将对数据库的修改永久化和废除,但是无论是提交和回滚,DM保证数据库在每个事务开始前、结束后是一致的。

为了提高事务管理的灵活性,DM提供了设置保存点(SAVEPOINT)语句和回滚到保存点语句。

保存点提供了一种灵活的回滚,事务在执行中可以回滚到某个保存点,在该保存点以前的操作有效,而以后的操作被回滚掉。

DM中的事务同样具有上述4个属性:原子性、一致性、隔离性和持久性。

2.并发操作与数据的不一致性
如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题,导致数据库中的数据的不一致性。

一个最常见的并发操作的例子是火车/飞机订票系统中的订票操作。

例如,在该系统中的一个活动序列:
①甲售票员读出某航班的机票张数余额A,设A=16;
②乙售票员读出同一航班的机票张数余额A,也是16;
③甲售票员卖出一张机票,修改机票张数余额A=A-1=15,把A写回数据库;
④乙售票员也卖出一张机票,修改机票张数余额A=A-1=15,把A写回数据库。

结果明明卖出两张机票,数据库中机票余额只减少1。

这种情况称为数据库的不一致性。

这种不一致性是由甲、乙两个售票员并发操作引起的。

在并发操作情况下,对甲、乙两个事务操作序列的调度是随机的。

若按上面的调度序列行,甲事务的修改就被丢失。

这是由于第4步中乙事务修改A并写回覆盖了甲事务的修改。

并发操作带来的数据库不一致性可以分为四类:丢失或覆盖更新、脏读、不可重复读和幻像读,上例只是并发问题的一种。

(1) 丢失或覆盖更新(lost update )
当两个或多个事务选择同一数据,并且基于最初选定的值更新该数据时,会发生丢失更新问题。

每个事务都不知道其它事务的存在。

最后的更新将重写由其它事务所做的更新,这将导致数据丢失。

上面预定飞机票的例子就属于这种并发问题。

事务1与事务2先后读入同一数据A=16,事务1执行A-1,并将结果A=15写回,事务2执行A-1,并将结果A=15写回。

事务2提交的结果覆盖了事务1对数据库的修改,从而使事务1对数据库的修改丢失了。

(2) 脏读
一个事务读取了另一个未提交的并行事务写的数据。

当第二个事务选择其它事务正在更新的行时,会发生未确认的相关性问题。

第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。

换句话说,当事务1修改某一数据,并将其写回磁盘,事务2读取同一数据后,事务1由于某种原因被撤销,这时事务1已修改过的数据恢复原值,事务2读到的数据就与数据库中的数据不一致,是不正确的数据,称为脏读。

例如,在下图中,事务1将C 值修改为200,事务2读到C 为200,而事务1由于某种原因撤销,其修改作废,C 恢复原值100,这时事务2读到的就是不正确的“脏“数据了。

(3) 不可重复读(nonrepeatable read )
事务1 事务2 时间 T1 T2 T3
事务1 事务2
T1
T2
T3
一个事务重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务修改过。

即事务1读取某一数据后,事务2对其做了修改,当事务1再次读数据时,得到的与第一次不同的值。

例如,在下图中,事务1读取B=100进行运算,事务2读取同一数据B ,对其进行修改后将B=200写回数据库。

事务1为了对读取值校对重读B ,B 已为200,与第一次读取值不一致。

(4) 幻像读
如果一个事务在提交查询结果之前,另一个事务可以更改该结果,就会发生这种情况。

这句话也可以这样解释,事务1按一定条件从数据库中读取某些数据记录后未提交查询结果,事务2删除了其中部分记录,事务1再次按相同条件读取数据时,发现某些记录神秘地消失了;或者事务1按一定条件从数据库中读取某些数据记录后未提交查询结果,事务2插入了一些记录,当事务1再次按相同条件读取数据时,发现多了一些记录。

产生上述四类数据不一致性的主要原因是并发操作破坏了事务的隔离性。

并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。

3. 并发场景列举
结合SQL 语句,列举各种并发情况(包括可能导致数据不一致性和对数据一致性不产生影响的情况)。

A 表示某一条数据,b 和c 都表示满足某一个标准的两条或多条数据,^表示“非”的意思,∈表示属于或包含于的意思,1表示第一个事务,2表示第二个事务。

事务1 事务2
时间 T2
(二)并发操作的调度
计算机系统对并行事务中并行操作的调度是随机的,而不同的调度可能会产生不同的结果,那么哪个结果是正确的,哪个是不正确的呢?
如果一个事务运行过程中没有其他事务在同时运行,也就是说没有受到其他事务的干扰,那么就可能认为该事务的运行结果是正常的或者预想的,因此将所有事务串行起来的调度策略是正确的调度策略。

虽然以不同的顺序串行执行事务也可能会产生不同的结果,但由于不会将数据库置于不一致状态,所以都可以认为是正确的。

由此可以得到如下结论:几个事务的并行执行是正确的,当且仅当其结果与按某一次序串行地执行它们的结果相同。

我们称这种并行调度策略为可串行化(serializable)的调度。

可串行性(serializability)是并行事
务正确性的唯一准则。

例如,现在有两个事务,分别包含下列操作:
事务1:读B;A=B+1;写回A;
事务2:读A;B=A+1;写回B;
假设A的初值为10,B的初值为2。

下图给出了对这两个事务的三种不同的调度策略,(a)和(b)为两种不同的串行调度策略,虽然执行结果不同,但他们都是正确的调度。

(c)中两个事务是交错执行的,由于执行结果与(a)、(b)的结果都不同,所以是错误的调度。

(d)中的两个事务也是交错执行的,由于执行结果与串行调度1(图(a))的执行结果相同,所以是正确的调度。

为了保证并行操作的正确性,DBMS的并行控制机制必须提供一定的手段来保证调度是可串行化的。

从理论上讲,在某一事务执行时禁止其他事务执行的调度策略一定是可串行化的调度,这也是最简单的调度策略,但这种方法实际上是不可行的,因为它使用户不能充分共享数据库资源。

目前DBMS普遍采用封锁方法(悲观方法,DM采用的就是这种方法,SQL Server也是采用的这种方法)来保证调度的正确性;即保证并行操作调度的可串行性。

除此之外还有其他一些方法,如时标方法、乐观方法等。

∙悲观并发控制
锁定系统阻止用户以影响其它用户的方式修改数据。

如果用户执行的操作导致应用
了某个锁,则直到这个锁的所有者释放该锁,其它用户才能执行与该锁冲突的操作。

该方法主要用在数据争夺激烈的环境中,以及出现并发冲突时用锁保护数据的成本
比回滚事务的成本低的环境中,因此称该方法为悲观并发控制。

∙乐观并发控制
在乐观并发控制中,用户读数据时不锁定数据。

在执行更新时,系统进行检查,查
看另一个用户读过数据后是否更改了数据。

如果另一个用户更新了数据,将产生一
个错误。

一般情况下,接收错误信息的用户将回滚事务并重新开始。

该方法主要用
在数据争夺少的环境内,以及偶尔回滚事务的成本超过读数据时锁定数据的成本的
环境内,因此称该方法为乐观并发控制。

∙时标并发控制
时标和封锁技术之间的基本区别是封锁是使一组事务的并发执行(即交叉执行)同
步,使用它等价于这些事务的某一串行操作;时标法也是使用一组事务的交叉执行
同步,但是使它等价于这些事务的一个特定的串行执行,即由时标的时序所确定的
一个执行。

如果发生冲突,是通过撤销并重新启动一个事务解决的。

事务重新启动,则赋予新的时标。

(三)封锁
封锁是事项并发控制的一个非常重要的技术。

所谓封锁就是事务T在对某个数据对象,例如,在标、记录等操作之前,先向系统发出请求,对其加锁。

加锁后事务T就对数据库对象有了一定的控制,在事务T释放它的锁之前,其他事务不能更新此数据对象。

1.封锁类型
DBMS通常提供了多种数据类型的封锁。

一个事务对某个数据对象加锁后究竟拥有什么样的控制是由封锁类型决定的。

基本的封锁类型有两种:排他锁(exclusive lock,简记为X锁)和共享锁(share lock简记为S锁)
排他锁又称为写锁。

若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。

这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。

共享锁又称为读锁。

若事务T对数据对象A加上S锁,则其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的锁。

这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁与共享锁的控制方式可以用下图的相容矩阵来表示。

在下图的封锁类型相容矩阵中,最左边一列表示事务T1已经获得的数据对象上的锁的类型,其中横线表示没有加锁。

最上面一行表示另一事务T2对同一数据对象发出的封锁请求。

T2的封锁请求能否被满足用Y和N表示,其中Y表示事务T2的封锁要求与T1已持有的锁相容,封锁请求可以满足。

N表示T2的封锁请求与T1已持有的锁冲突,T2请求被拒绝。

Y=Y
2.封锁粒度
X锁和S锁都是加在某一个数据对象上的。

封锁的对象可以是逻辑单元,也可以是物理单元。

例如,在关系数据库中,封锁对象可以是属性值、属性值集合、元组、关系、索引项、整个索引、整个数据库等逻辑单元;也可以是页(数据页或索引页)、块等物理单元。

封锁对象可以很大,比如对整个数据库加锁,也可以很小,比如只对某个属性值加锁。

封锁对象的大小称为封锁的粒度(granularity)。

封锁粒度与系统的并发度和并发控制的开销密切相关。

封锁的粒度越大,系统中能够被封锁的对象就越小,并发度也就越小,但同时系统开销也越小;相反,封锁的粒度越小,并发度越高,但系统开销也就越大。

因此,如果在一个系统中同时存在不同大小的封锁单元供不同的事务选择使用是比较理想的。

而选择封锁粒度时必须同时考虑封锁机构和并发度两个因素,对系统开销与并发度进行权衡,以求得最优的效果。

一般说来,需要处理大量元组的用户事务可以以关系为封锁单元;需要处理多个关系的大量元组的用户事务可以以数据库为封锁单位;而对于一个处理少量元组的用户事务,可以以元组为封锁单位以提高并发度。

3.封锁协议
封锁的目的是为了保证能够正确地调度并发操作。

为此,在运用X 锁和S 锁这两种基本封锁,对一定粒度的数据对象加锁时,还需要约定一些规则,例如,应何时申请X 锁或S 锁、持锁时间、何时释放等。

我们称这些规则为封锁协议(locking protocol )。

对封锁方式规定不同的规则,就形成了各种不同的封锁协议,它们分别在不同的程度上为并发操作的正确调度提供一定的保证。

本节介绍保证数据一致性的三级封锁协议和保证并行调度可串行性的两段锁协议,下一节将介绍避免死锁的封锁协议。

(5) 保证数据一致性的封锁协议――三级封锁协议
对并发操作的不正确调度可能会带来四种数据不一致性:丢失或覆盖更新、脏读、不可重复读和幻想读。

三级封锁协议分别在不同程度上解决了这一问题。


1级封锁协议
1级封锁协议的内容是:事务T 在修改数据R 之前必须先对其加X 锁,直到事务结束才释放。

事务结束包括正常结束(commit )和非正常结束(rollback )。

1级封锁协议可以防止丢失或覆盖更新,并保证事务T 是可以恢复的。

例如,下图使用1级封锁协议解决了定飞机票例子的丢失更新问题。

上图中,事务1在读A 进行修改之前先对A 加X 锁,当事务2再请求对A 加X 锁时被拒绝,只能等事务1释放A 上的锁。

事务1修改值A=15写回磁盘,释放A 上的X 锁后,事务2获得对A 的X 锁,这时他读到的A 已经是事务1更新过的值15,再按此新的A 值进行运算,并将结果值A=14回到磁盘。

这样就避免了丢失事务1的更新。

在1级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和脏读。

② 2级封锁协议 2级封锁协议的内容是:1级封锁协议加上事务T 在读取数据R 之前必须先对其加S 锁,读完后即可释放S 锁。

2级封锁协议除防止了丢失或覆盖更新,还可进一步防止脏读。

例如,下图使用2级封锁协议解决了脏读的问题。

下图中,事务1在对C 进行修改之前,先对C 加X 锁,修改其值后写回磁盘。

这时事务2请求C 加上S 锁,因T1已在C 上加了X 锁,事务2只能等待事务1释放它。

之后事
T4
T1 T2
T3
T5
务1因某种原因被撤销,C恢复为原值100,并释放C上的X锁。

事务2获得C上的S锁,读C=100。

这就避免了事务2脏读数据。

在2级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。

③3级封锁协议
3级封锁协议的内容是:1级封锁协议加上事务T在读取数据之前必须先对其加S锁,直到事务结束才释放。

3级封锁协议除防止丢失或覆盖更新和不脏读数据外,还进一步防止了不可重复读和幻想读。

例如下图,使用3级封锁协议解决了不可重复读和幻像读问题。

事务1
T1
T2
T3
T4
事务1
时间
T2
T3
T4
上图中,事务1在读A ,B 之前,先对A ,B 加S 锁,这样其他事务只能再对A ,B 加S 锁,而不能加X 锁,即其他事务只能读A ,B ,而不能修改它们。

所以当事务2为修改B 而申请对B 的X 锁时被拒绝,使其他无法执行修改操作,只能等待事务1释放B 上的锁。

接着事务1为验算再读A ,B ,这时读出的B 仍是100,求和结果仍为150,即可重复读。

上述三级协议的主要区别在于什么操作需要申请封锁以及何时释放锁(即持锁时间)。

三级封锁协议可以总结为下表。

(6) 保证并行调度可串行性的封锁协议――两段封锁协议
可串行性是并行调度正确性的唯一准则,两段锁(two-phase locking ,简称2PL )协议是为保证并行调度可串行性而提供的封锁协议。

两段封锁协议规定:
①在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁,而且②在释放一个封锁之后,事务不再获得任何其他封锁。

所谓“两段”锁的含义是,事务分为两个阶段,第一阶段是获得封锁,也称为扩展阶段,第二阶段是释放封锁,也称为收缩阶段。

例如,事务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; 则事务1遵守两段封锁协议,而事务2不遵守两段封锁协议。

可以证明,若并行执行的所有事务均遵守两段锁协议,则对这些事务的所有并行调度策略都是可串行化的。

因此我们得出如下结论:所有遵守两段锁协议的事务,其并行的结果一定是正确的。

需要说明的是,事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件。

即可串行化的调度中,不一定所有事务都必须符合两段封锁协议。

例如,在下图中,(a )和(b )都是可串行化的调度,但(a )遵守两段锁协议,(b )不遵守两段锁协议。

4.死锁和活锁
封锁技术可以有效地解决并行操作的一致性问题,但也带来一些新的问题,即死锁和活锁的问题。

(1)活锁
如果事务T1封锁了数据对象R后,事务T2也请求封锁R,于是T2等待。

接着T3也请求封锁R。

T1释放R上的锁后,系统首先批准了T3的请求,T2只得继续等待。

接着T4也请求封锁R,T3释放R上的锁后,系统又批准了T4的请求……,T2有可能就这样永远等待下去。

这就是活锁的情形,如下图所示。

封锁子系统按请求封锁的先后次序对这些事务排队,该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁。

(2)死锁
如果事务T1封锁了数据A,事务T2封锁了数据B。

之后T1又申请封锁数据B,因T2已封锁了B,于是T1等待T2释放B上的锁。

接着T2又申请封锁A,因T1已封锁了A,T2也只能等待T1释放A上的锁。

这样就出现了T1在等待T2,而T2又在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁。

如下图所示。

点,操作系统中解决死锁的方法并不一定合适数据库系统。

目前在数据库中解决死锁问题主要有两类方法,一类方法是采取一定措施来预防死锁的发生,另一类方法是允许发生死锁,采用一定手段定期诊断系统中有无死锁,若有则解除之。

①死锁的预防
在数据库系统中,产生死锁的原因是两个或多个事务都已封锁了一些数据对象,然后又都请求对已为其他事务封锁的数据对象加锁,从而出现死锁等待。

防止死锁的发生其实就是要破坏产生死锁的条件。

预防死锁通常有两种方法。

◆一次封锁法
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。

例如,在上图的例子中,如果事务T1将数据对象A和B一次加锁,T1就可以执行下去,而T2等待。

T1执行完后释放A,B上的锁,T2继续执行。

这样就不会发生死锁。

一次封锁法虽然可以有效地防止死锁的发生,但也存在问题。

第一,一次就将以后要用到的全部数据加锁,势必扩大了封锁的范围,从而降低了系统的并发度。

第二,数据库中数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难实现精确地确定每个事务所要封锁的数据对象,只能采取扩大封锁范围,将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度。

◆顺序封锁法
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序执行封锁。

在上例中,我们规定封锁顺是A,B,T1和T2都按此顺序封锁,即T2也必须先封锁A。

当T2请求A的封锁时,由于T1已经封锁住A,T2就只能等待。

T1释放A,B上的锁后,T2继续运行。

这样就不会发生死锁。

顺序封锁法同样可以有效地防止死锁,但也同样存在问题。

第一,数据库系统中可封锁的数据对象及其众多,并且随数据的插入、删除等操作而不断地变化,要维护这样极多而且变化的资源的封锁顺序非常困难,成本很高。

第二,事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序取施加封锁。

例如,规定数据对象的封锁顺序为A,B,C,D,E。

事务T3起初要求封锁数据对象B,C,E,但当它封锁B,C后,才发现还需要封锁A,这样就破坏了封锁顺序。

可见,在操作系统中广为采用的预防死锁的策略并不很适合数据库的特点,因此DBMS 在解决死锁的问题上更普遍采用的是诊断并解除死锁的方法。

②死锁的诊断与解除
数据库系统中诊断死锁的方法与操作系统类似,即使用一个事务等待图,它动态地反映所有事务的等待状况。

并发控制子系统周期性地(比如每隔1分钟)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁。

关于诊断死锁的详细讨论请参阅操作系统的有关书籍。

DBMS的并发控制子系统一旦检测到系统中存在死锁,就要设法解除。

通常采用的方。

相关文档
最新文档