【专业课学习】《数据库原理》第八章习题

136 阅读11分钟

seybwdeacyx (1).jpg

8.1 解释事务(Transcation)。简述事务的四个特性。

事务是由用户(调用API的程序员)定义的一组数据库读写操作序列。一个事务执行时其所有的操作,要么全部执行,要么全部不执行(即操作执行到一半可能会回滚回去,相当于从没执行过)。

事务的ACID特性:

  • 原子性(Atomicity):事务的原子性是指事务是数据库操作的最小单位,不可再分。一个事务中的所有操作要么全部完成,即提交(commit),要么全部不完成,即回滚(rollback)。
  • 一致性(Consistency): DBMS保证事务的操作状态是正确的(即事务应保证数据从一个正确的状态转移到另一个正确的状态),符合一致性的操作规则,不能出现三种典型的不一致性。它是进一步由隔离性来保证的。
  • 隔离性(Isolation): DBMS保证并发执行的多个事务之间互相不受影响。例如两个事务T1和T2, 即使并发执行,其最终效果也相当于先执行了T1,再执行T2;或者先执行了T2, 再执行T1。
  • 持久性(Durability):DBMS保证事务一旦成功提交,则事务对数据库的更新永久生效。它由DBMS的恢复管理子系统保证。

8.2 解释并发控制。简述并发控制与数据共享的关系。

并发控制:为了确保 ACID 特性,而对并发事务采取封锁和调度等技术进行控制。

并发控制与数据共享的关系:

  • 并发控制是保证数据共享安全和可靠性的必要手段。 数据共享会导致数据冲突的风险增加,如果不采取并发控制措施,可能会导致数据的一致性和完整性遭到破坏。
  • 数据共享是并发控制的应用场景。 并发控制的目的是保证数据的一致性和完整性,而数据共享恰恰是并发访问数据的典型场景。
  • 并发控制粒度越大,其共享度越低;并发控制粒度越小,其共享度越高。

8.3 简述并发事务导致的数据不一致。

并发事务导致的数据不一致

(一) 丢失修改

数据库系统基础讲义第22讲数据库事务处理技术-并发控制!@#$%^&() +.png

以下的MS Transact-SQL代码模拟了丢失修改的现象:

--事务T1
begin tran T1 
    declare @Var int;
    select @Var=Salary from Emp where EName='张三';
    waitfor delay '00:00:10';
    update Emp set Salary=@Var+600 where EName='张三';
commit tran T1;

--事务T2
begin tran T2
    --模拟T2比T1晚一点执行
    waitfor delay '00:00:03';
    declare @Var int;
    select @Var=Salary from Emp where EName='张三';
    waitfor delay '00:00:10';
    update Emp set Salary=@Var - 500 where EName='张三';
commit tran T2;

假设一开始张三的工资是5000,但在Sql Server中执行上述的模拟代码可以发现工资会变为4500,而不是我们预期的5000+600-500=5100。

(二) 读脏数据

数据库系统基础讲义第22讲数据库事务处理技术-并发控制!@#$%^&() +.png

以下的MS Transact-SQL代码模拟了读脏数据的现象:

begin tran T1
	declare @Var int;
	select @Var=Salary from Emp with(nolock) where EName='张三';
	update Emp set Salary=@Var+600 where EName='张三';
	waitfor delay '00:00:10';
rollback tran T1;

-- 假设T2在T1执行期间执行
begin tran T2
	declare @Var int;
	select @Var=Salary from Emp with(nolock) where EName='张三';
	if (@Var != 5000) print '读脏数据!';
commit tran T2;

在事务T1中,尝试修改了张三的工资,但最终又放弃了修改,因此张三的工资还是5000。如果在T1等待期间T2执行,那么就会读取到张三的工资为5600这个错误的数据。

(三) 不可重读

数据库系统基础讲义第22讲数据库事务处理技术-并发控制!@#$%^&() +.png

以下的MS Transact-SQL代码模拟了不可重读的现象:

begin tran t1
    declare @var1 int, @var2 int;
    select @var1=Salary from Emp where EName='张三';
    waitfor delay '00:00:10';
    select @var2=Salary from Emp where EName='张三';
    if (@var1 != @var2) print '不可重读!';
commit tran t1;

begin tran t2
    declare @var int;
    select @var=Salary from Emp where EName='张三';
    update Emp set Salary=@Var+600 where EName='张三';
commit tran t2;

8.4 解释封锁。简述常用的封锁。

封锁:事务T在对数据对象D操作之前,先对D加锁L,以声明对D的控制权限,在T释放L之前,在所有事物都遵守封锁协议的前提下,其它事务无法对D上锁,导致不能对D进行操作。

常用封锁类型:

  • 排它锁(Exclusive Locks,X锁):某数据对象D被某事务T上X锁,表示该事务声明其对D准备进行读写操作,故为了保证其他事务在T执行期间避免对D的读写操作,要求在X锁被释放前其他事务不能对D上X锁和S锁。

  • 共享锁(Shared Locks,S锁):某数据对象D被某事物T上S锁,表示该事务准备对D进行读操作,故为了防止其他事物在T执行期间修改D的信息,要求S锁被释放前其他事务不难对D进行写操作(即其他事务不能上X锁),但读操作是被允许的(即其他事务也可以上S锁然后过来读D)。

无标题.png

8.5 解释封锁协议?简述常用的封锁协议。

封锁协议(Locking Protocol):为了解决并发事务所导致的数据不一致,利用 X 锁、S 锁等对数据对象进行封锁时遵循的特定规则。

常用封锁协议:

一级封锁协议
  • 特点:事务T在修改数据对象D时,必须先对D加X锁,直到事务结束释放X锁。
  • 优点:由于T事务结束之前对象D一直被X锁封锁,因此其他事务在T结束前始终无法对D进行写操作(因为在该协议要求下所有事务在写操作之前都要先给数据对象加X锁,而X锁是无法加在已有X锁的数据对象上的),这就解决了丢失修改的问题。
  • 缺陷:
    • 不能解决读脏数据:因为该协议并没有规定事务读数据对象之前需要加锁,因此事务的读操作可以随便进行,当然不会受到已有的X锁影响啦...
    • 不能解决不可重读:这个是显然的

无标题.png

以下MS Transact-SQL代码模拟了如何利用一级封锁协议解决丢失修改的问题:

begin tran t1
    declare @var int;
    -- TABLOCKX 表示强制独占表级锁,这个锁在事务期间阻止任何其他事务使用这个表
    select @var=Salary from Emp with(TABLOCKX) where EName='张三'
    waitfor delay '00:00:10';
    update Emp set Salary=@var+600 where EName='张三';
commit tran t1;

begin tran t2
    waitfor delay '00:00:03';
    declare @var int;
    -- 因此由于事务T1已经对Emp表上了X锁,该事务在T1执行完成前会处于挂起状态
    select @var=Salary from Emp with(TABLOCKX) where EName='张三'
    waitfor delay '00:00:10';
    update Emp set Salary=@var-500 where EName='张三';
commit tran t2;
二级封锁协议
  • 特点:在一级封锁协议的基础上,事务T在读取数据对象D时,必须先对D加S锁,读取结束立即释放S锁。
  • 优点:对一级封锁协议进行修补,解决了读脏数据的问题。这是因为事务T执行的时候可能会读到数据对象D的脏数据的根源是读操作发生在来自其他事务的对D数据对象进行修改与回滚的操作之间,导致T执行读操作读到的并不是最终的修改结果。而二级封锁协议可以保证其他事务(比如T')对数据对象D执行读操作到T'提交这期间(即从T'对D上X锁到X锁被释放期间),事务T中尝试读D的操作由于需要上S锁却一直上不上去,会一直处于"等待"状态,直到T'提交后才能顺利执行。这就从根本上消除了T中读D操作会在T'从执行写操作到执行回滚操作这期间执行的可能性。
  • 缺陷:不能解决不可重读。这是因为在二级封锁协议之下,事务T对D上的S锁在读操作结束之后就会被马上释放掉,这个时候另一个事务T'又可以对D上X锁并执行写操作,当T'执行完毕提交后,倘若T中又有针对D的读操作发生,这个时候就仍然会出现两次读操作结果不一致的问题。

无标题.png

以下MS Transact-SQL代码模拟了如何利用二级封锁协议解决读脏数据的问题:

begin tran T1
    declare @Var int;
    -- 二级封锁协议包含一级封锁协议
    -- 因此写操作需要加X锁
    select @Var=Salary from Emp with(tablockx) where EName='张三';
    update Emp set Salary=@Var+600 where EName='张三';
    waitfor delay '00:00:10';
rollback tran T1;

begin tran T2
    declare @Var int;
    -- 在Sql Server中,调用select语句默认会申请读操作完成后立即释放的共享锁,
    -- 因此由于事务T1已经对Emp表上了X锁,该事务在T1执行完成前会处于挂起状态
    select @Var=Salary from Emp where EName='张三';
    if (@Var != 5000) print '读脏数据!';
commit tran T2;
三级封锁协议
  • 特点:在二级封锁协议的基础上,事务T在读取数据对象D时,必须先对D加S锁,直到事务结束释放S锁。
  • 优点:对二级封锁协议进行修补,解决了不可重读的问题。理解了二级封锁协议,三级封锁协议就很好理解了。既然事务T中操作在两次读数据对象D之间会出现D被其他事务中操作修改的情况,那我直接让事务T从开始读数据到提交这期间S锁始终不释放,就解决问题了~

无标题.png

以下MS Transact-SQL代码模拟了如何利用三级封锁协议解决不可重读的问题:

begin tran t1
    declare @var1 int, @var2 int;
    -- 在Sql Server中holdlock表示保持至整个事务结束的S锁
    select @var1=Salary from Emp with (holdlock) where EName='张三';
    waitfor delay '00:00:10';
    select @var2=Salary from Emp where EName='张三';
    if (@var1 != @var2) print '不可重读!';
commit tran t1;

begin tran t2
    declare @var int;
    -- 由于T1已经为Emp表上了持久性的S锁,这里想给该表上X锁(执行读操作)的话就只能被挂起直到事务T1完全执行完毕为止了
    select @var=Salary from Emp with (tablockx) where EName='张三';
    update Emp set Salary=@Var+600 where EName='张三';
commit tran t2;
两段锁协议

两段锁协议(Two Phase Locking,2PL):所有事务按照“加锁阶段”和“解锁阶段”两个阶段,对数据对象进行加锁和解锁。即:在加锁阶段,所有事务只能加锁,不能解锁;而在解锁阶段,则所有事务只能解锁,不能加锁。

可以通过数学归纳法证明,两段封锁协议是可以保证并发的冲突可串行性的。

不难看出,若遵守三级封锁协议,则一定遵守两段锁协议。(因为三级封锁协议下直接把所有的X锁和S锁的释放实际全部推迟到了事务结束)

8.6 解释并发事务的串行调度和并行调度。简述二者的关系。

并发事务的串行调度:针对并发事务,按照一定的顺序依次执行每一个事务

并发事务的并行调度:针对并发事务,交错地执行若干个事务中的操作

二者关系:

(1)并发事务在串行执行过程中,事务之间不会产生相互影响,尽管并发事务不同的串 行调度的执行结果可能不同,但不会导致数据不一致。因此并发事务的串行调度是正确调度。

(2)并发事务在并行执行过程中,事务之间可能发生相互影响,因此并发事务的并行调度可能是错误调度。

(3)因为并发事务的串行调度总是正确的,所以可以使用并发事务的串行调度来验证并发事务的并行调度的正确性,即考察事务并行调度的执行结果是否与串行调度一致。

8.7 解释并发事务的可串行化。简述两段锁协议与并发事务可串行化的关系。

并发事务的可串行化:如果并发事务的并行调度的执行结果与并发事务的某个串行调度的 执行结果相同,则称该并发调度是可串行化的。

如果并发事务的并行调度遵守两段锁协议,则并发调度一定(冲突)可串行化。

如果并发事务的并行调度不遵守两段锁协议,则并发调度也可能可串行化。即:两段锁协 议是并发事务可串行化的充分条件,而非必要条件。

8.9 解释活锁和死锁?简述活锁和死锁的解除方法。

活锁:由于授权封锁的随机性,导致事务T的封锁请求一直处于等待状态的封锁。即:T封锁一个数据对象时,T始终处于等待状态。

解决方法:采用先来先服务的授权策略。并发事务请求封锁数据对象时,按照请求封锁的先后次序依次进行授权。

死锁:在并发事务的多个事务进行封锁请求时,分别需要为对方加锁,而对方又不允许加锁,从而造成多方处于一直等待状态。

死锁的解决方法:(1)预防死锁。(2)诊断与解除。

无标题.png