数据库的并发控制

所谓并发操作,是指在多用户共享的系统中,许多用户可能同时对同一数据进行操作。

并发操作带来的问题是数据的不一致性主要有3类:丢失更新、不可重复度和读脏数据其主要的原因是事务的并发操作破坏了事务的隔离性

DBMS的并发控制子系统负责协调并发事务的执行,保证数据库的完整性不受破坏,避免用户得到不正确的数据。

事务调度

  • 串行调度(Serial Schedule)

是指多个事务依次串行执行,且只有当一个事务的所有操作都执行完后才执行另一个事务的所有操作。

对两个事务T0,T1,不论是先执行T0后执行T1,还是先执行T1后执行T0,只要是串行调度,执行的结果都是稳定和正确的

  • 并发调度(Concurrent Schedule)

利用分时的方法同时处理多个事务。对于N个事务进行并发调度,情况会变得复杂得多,调度方案远大于N!个,并且并发调度的结果有可能是错误的

  • 可恢复调度(Recoverable Schedule)

事务Tj要读事务Ti写的数据时,事务Tj必须要先于事务Ti提交

并发操作带来的问题

image-20220315095757867

  • 丢失修改

当两个或多个事物读入同一数据并修改,会发生丢失丢该的问题,如上图丢失修改案例,T 1事务的A=A-1修改操作, 被T2 事务的A=A-1所覆盖,也就是丢失了T 1 事务的修改操作.

  • 不可重复读

是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。

如上图,事务T1分别在T2和T9时刻读取了B的值,但是事务T2在t5时刻对B的值进行了修改操作,导致T1事务两次读取B的值不同,从而检验运算C=A+B不对。

也就是在同一事务内两次读取同一数据输出结果不同,是因为在两次读取的时间间隙里,其他事务对该数据进行了修改。

  • 读脏数据

T1事务修改C数据后,未提交到数据库,T2事务读取C数据为200,但是t7时刻T1事务撤销了对C数据的修改,即C恢复原值,那T2事务读取到的数据存在不一致(无效),称之为读脏数据

这3种情况是发生在事务并行处理的过程中,原因是多个事务对相同数据的访问,干扰了其他事务的处理,产生了数据的不一致性,是事务隔离性的破坏导致了数据的不一致性。

问题的焦点在于事务在读写数据是不加以控制而相互干扰,解决问题的方法是如何保证事务的隔离性入手。

数据库事务的隔离级别如下:

由低到高依次为Read Uncommitted 、Read Committed 、Repeatable Read 、Serializable

这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。 (非常重要)

image-20220315101443061

并发调度的可串行性

数据库系统必须控制事务的并发执行以保证数据库处于一致性状态。

  • 可串行化的调度

多个事务的并发执行是正确的,当且仅当其结果与某一次序串行的执行它们时的结果相同,称这种调度策略是可串行化的调度(Serializability Schedule)。

可串行性是并发事务正确性的准则,按这个准则规定,一个给定的并发调度,当且仅当它是可串行化的才认为是正确调度。

  • 冲突可串行化

冲突可串行化:若调度S 与一个串行调度S ∗冲突等价,则S是冲突可串行化的。

  • 冲突可串行化判定

在设计并发控制机制时,必须证明该机制产生的调度是否可串行化。

并发控制技术

并发事务控制的最常用手段是加锁,该方法只允许事务访问当前持有锁的数据项。

常用锁:排它锁和共享锁。

排他锁(Exclusive Locks,X锁)也称为写锁,用于对数据进行写操作时进行锁定,如果事务T对数据A加上X锁后,就只允许事务T对读取和修改数据A,其他事务对数据A不能再加任何锁,也不能读取和修改数据A,知道事务T释放A上的锁。

共享锁(Share Locks,S锁)也称为读锁,用于对数据读操作的锁定。如果事务T对数据A加上了S锁后,事务T就只能读数据A但不可以修改,其他事务可以在对数据A加S锁来读取,只要数据A上有S锁,任何事务都只能对其加S锁读取而不能加X锁修改数据。

简言之,若T事务对A数据加了X锁,其他事务对数据A不能加X锁和S锁;若T事务对A数据加了S锁,其他事务对数据A能加S锁,不能加X锁。

两段锁协议

通过封锁协议来解决在保证事务的一致性的前提下尽可能提高并发性问题,封锁协议主要是三级封锁协议和两段锁协议

  • 1.封锁协议

(1)一级封锁协议:是事务T在修改数据A之前必须先对其加X锁,直到事务结束才释放X锁,用来解决丢失修改问题。

(2)二级封锁协议:是在一级封锁协议基础上加上事务T在读取数据A之前必须对其加上S锁,读完后即可释放S锁,解决读脏数据的问题。

(3)三级封锁协议:是在一级封锁协议基础上加上事务T在读取数据A之前必须对其加上S锁,直到事务结束才可释放S锁,解决了不可重复读的问题。

  • 2.两段锁协议

两段锁协议是指任何数据进行读写之前必须对该数据加锁,在释放一个封锁之后,事务不再申请和获得任何其他封锁。

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

如果事务都遵循两段锁协议,那么他们的并发调度是可串行化的。

两段锁是可串行化的充分不必要条件。也就是如果事务不遵循两段锁协议,那么它们的调度可能是可串行也可能不可串行。

注意:采用两段锁协议也有可能产生死锁,因为每个事务都不能及时解除被他封锁的数据,可能会导致多个事务互相都要求对方已封锁的数据不能继续运行。

  • 3.活锁和死锁

所谓死锁:是指两个以上的事务分别请求封锁对方已经封锁的数据,导致长期等待而无法继续运行下去的现象。

预防死锁:一次封锁法和顺序封锁法。

解决死锁: DBMS的并发控制子系统一旦检测到系统中存在死锁,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有的锁,使其它事务得以继续运行下去。当然,对撤消的事务所执行的数据修改操作必须加以恢复。

相关真题

2014年54-55题

事务T1中有两次查询学生表中的男生人数,在这两次查询执行中间,事务T2 对学生表中加入了一条男生记录,导致T1两次查询的结果不一致,此类问题属于(54),为解决这一问题,应采用的隔级别是(55)。

(54) A.不可重复读 B.读脏数据 C.丢失修改 D.幻影现象

(55) A.Read Uncommitted B.Read Committed C.Repeatable Read D.Serializable

【答案】D D

同一事务内,对数据库的两次条件完全相同的査询,其访问的记录应该完全相同。

若两次访问中间数据库被其他事务改变,倒得两次査询所访问的记录不同,称为幻影现象

加锁机制的封锁对象分为表和记录,锁的类型相应称为表级锁和行级锁。

当加行级锁时,未加锁的记录可能被修改为符合查询条件,或者新插入的记录符合查询条件,导致二次查询访问的记录数增加。而当采用表级锁时,表中所有记录在同一事务的两次查询中间是不允许改变的,即可解决此问题。

加表级锁的隔离级别是Serializable

2015年45/46题

假设系统中只有事务T1和T2,两个事务都要对数据D1和D2进行操作。

若T1对D1已加排它锁,T1对D2已加共享锁

那么T2对D1(45),

A.加共享锁成功,加排它锁失败 B.加共享锁、加排它锁都失败

C.加共享锁、加排它锁都成功 D.加排它锁成功,加共享锁失败

那么T2对D2(46)。

A.加共享锁成功,加排它锁失败 B.加共享锁、加排它锁都失败

C.加共享锁、加排它锁都成功 D.加排它锁成功,加共享锁失败

【答案】B A

并发事务如果对数据读写时不加以控制,会破坏事务的隔离性和一致性

控制的手段就是加锁,在事务执行时限制其他事务对数据的读取。

在并发控制中引入两种锁:排它锁(Exclusive Locks,简称X锁)和共享锁(Share Locks,简称S锁)。

排它锁又称为写锁,用于对数据进行写操作时进行锁定。

如果事务T对数据A加上X锁后,就只允许事务T读取和修改数据A**,其他事务对数据A不能再加任何锁,从而也不能读取和修改数据A**,直到事务T释放A上的锁。

共享锁又称为读锁,用于对数据进行读操作时进行锁定。如果事务T对数据A加上了S锁后,事务T就只能读数据A但不可以修改,其他事务可以再对数据A加S锁来读取,只要数据A上有S锁,任何事务都只能再对其加S锁读取而不能加X锁修改

::: danger

如果已经加了排它锁,不能再加任何锁。

如果已经加了共享锁,还可以添加共享锁。

:::

2015年54-55题

如右图所示的调度,其中事务T1、T2仅对数据项A、B进行操作,则该调度(54);

img

A.满足两段锁协议、不发生死锁 B.满足两段锁协议、会发生死锁

C.不满足两段锁协议、不发生死锁 D.不满足两段锁协议、会产生死锁

【答案】B

【解析】本题考查事务调度的知识。

事务的执行由DBMS进行调度,在执行事务的过程中加入相关锁指令以控制事务满足ACID属性。

常用的方式是两段锁协议(2PL)

即事务的加锁和解锁分为两个阶段,第一阶段为锁增长阶段,只能加锁不能解锁,第二阶段为锁减少阶段,只能解锁不能加锁。

图中的调度,事务T1对B、A两个数据项加锁中间无解锁指令,满足2PL协议;

事务T2对A、B两个数据项加锁中间无解锁指令,也满足2PL协议。

2PL协议不能避免死锁。

图中事务T1先对数据项B加了独占锁,事务T2先对数据A加了共享锁;

随后事务T2申请数据项B上的共享锁,只能等待事务T1释放B上的独占锁;

事务T1又请数据项A上的独占锁,只能等待事务T2释放A上的共享锁。

两个事务相互等待造成死锁。

死锁的解除由DBMS来完成。

需要在造成死锁的多个事务中选择一个回滚代价最小的事务进行强制回滚,并将该事务置于事务队列中稍后执行。

图中事务T1对数据B己经做了修改,事务T2只是读取了数据A,相对而言,回滚事务T2代价最小

假如该调度已经产生死锁,如果要从事务T1,T2中进行回滚以解除死锁,从代价最小的角度考虑,应回滚事务(55)。

(55)A.T1 B.T2 C.T1和T2 D.T1或T2

【答案】B