跳转至

Chapter14 Concurrency Control


14.1 Lock-Based Protocols

访问数据需要获得相应的锁。

  • exclusive lock (X) 排他锁:可以同时获得数据的读和写权限
  • shared lock (S) 共享锁:只能获得数据的读权限

Lock-Compatibility Matrix:

alt text

这个矩阵表明:同一个数据上可以有多个共享锁,分别被不同事务持有;但最多只能有一个排他锁,且有排他锁时不能有共享锁。

Two-Phase Locking Protocol:

两阶段封锁协议分为:

  • Growing Phase: 事务可以获得锁,但不能释放锁;
  • Shrinking Phase: 事务可以释放锁,但不能获得锁。

alt text

Note

这里的两个阶段是针对于同一事务而言,而不是整体而言。因此,不同事务的lock point(该事务获得最后一个锁的时间)也不尽相同。

两阶段封锁协议最优雅的地方在于其保证了冲突可串行化。也就是说,只要所有事务都遵循两阶段封锁协议,即一开始先获得锁,一旦开始释放锁就不能再获得锁,就一定是冲突可串行化的。

此外,串行化的结果也可以根据达到lock point的顺序来决定。

Proof

冲突可串行化$\Longleftrightarrow$前驱图无环
如果前驱图有环,因为一个箭头对应的前后操作是冲突的,意味着一个T1先放锁T2再加锁的过程(LP(T1)<LP(T2)),而另一个箭头意味着T2先放锁T1再加锁的过程(LP(T1)>LP(T2)),这在两阶段封锁协议下是矛盾的。 因此,由反证法即可得证。

alt text

至于按照lock point的顺序排序,同样结合前驱图,lock point最小的事务一定是最早加锁的事务,入度为0,最早执行;去掉这个事务,剩下的事务以此类推。

Note

如果重新定义lock point为释放第一个锁的时间,那么同样可以得到一个可能不同的串行结果。

然而,两阶段封锁协议并不能保证无死锁

然而,当前的两阶段封锁协议并不能保证可恢复,需要严格两阶段封锁(strict two-phase locking),即X锁需要在事务提交后才能释放,这样可以做到无级联恢复。

此外,还有强两阶段封锁(rigorous two-phase locking),即X锁和S锁都需要在事务提交后才能释放。此时,串行次序也可以根据事务提交的顺序来决定。

两阶段封锁协议是冲突可串行化的充分条件,但不是必要条件。一个很简单的反例就是:每个事务各自访问的数据都不同,那么一定是冲突可串行化的,但一个事务怎么访问对应的数据是随意的,也就是说不一定遵循两阶段封锁协议。

Lock Conversions:

在原本的两阶段封锁协议上进一步修改,允许锁的转换。因此,两阶段有所更新:

  • Growing Phase: 事务可以获得锁,或者将S锁转换为X锁(lock-upgrade)
  • Shrinking Phase: 事务可以释放锁,或者将X锁转换为S锁(lock-downgrade)

该协议同样是冲突可串行化的。

Automatic Acquisition of Locks:

事务在读写时的加锁不需要显示声明。

read:

alt text

write:

alt text

Lock Table:

事务在数据上的锁由数据结构lock table集中维护。

alt text

我们可以将lock table看作一个哈希表,每一个数据都会对应一个哈希值,在图中用白色矩形表示;不同的数据可能会有相同的哈希值,这些数据相连,即图中的每一行。

对于每一个数据,其各自底下有相应的上锁的事务和等待的事务。上锁的事务用深蓝色方形表示,等待的事务用浅蓝色方形表示。

Graph-Based Protocols:

图协议对数据项组成的集合$D=\{d_1,d_2,...,d_h\}$加上了偏序关系。

如果数据项$d_i\rightarrow d_j$,那么任何一个要访问$d_i$和$d_j$的事务,必须先访问$d_i$再访问$d_j$。

此时,$D$应当看作有向无环图,称为database graph

Tree Protocol:

树协议是图协议的一种。如果对事务有先验知识,可以使用树协议。

alt text

树协议的图是针对每个事务而言的,每个节点都是一个数据。

  • 只使用X锁;
  • 第一个锁可以加在任何一个数据上,之后,如果一个数据想要加锁,前提是其父节点对应的数据也一定要加锁;
  • 可以随时释放锁;
  • 同一个数据不能释放锁后再加锁

树协议既可以保证冲突可串行化,又可以保证没有死锁;但树协议不能保证可恢复性和无级联调度,也会对许多不必要访问的数据上锁。

Note

不能通过两阶段封锁协议生成的调度,可能可以通过树协议生成; 不能通过树协议生成的调度,可能可以通过两阶段封锁协议生成。


14.2 Deadlock Handling

alt text

Deadlock Prevention:

以下是一些预防策略:

  • 对于每个事务,要么一次性获得全部需要的锁,要么什么锁都不要,只是等待;
  • 对所有数据添加偏序关系,一个事务对数据的上锁顺序也要严格按照偏序关系来进行(例如树协议)。

Timeout-Based Schemes:

等待超过一定时间就回滚,但有时并没有死锁,也会迫使不必要的回滚。

依然存在starvation的可能。

Wait-Die:

非抢占式(non-preemptive)

对每个事务分配一个时间戳,表示其“年龄”,年老的事务可以等待年轻的事务,但年轻的事务一旦无法立刻得到想要的锁就会立刻回滚重开。

Wound-Wait:

抢占式(preemptive)

同样也有年龄的概念,年轻的事务可以等待年老的事务,但年老的事务一旦无法立刻得到想要的锁就会“杀死”年轻的事务来抢夺锁,迫使年轻的事务回滚重开。

比wait-die产生的回滚次数少。

Note

wait-die和wound-wait都保证年老的事务比年轻的事务优先级更高,避免starvation。

Deadlock Detection:

由于lock table包含所有的锁信息,因此可以定期检查是否有死锁。

通过wait-for graph来检查,节点为事务,有向边表示事务的等待,若发现成环则表示存在死锁(充要条件)。

alt text

Deadlock Recovery:

当检测到死锁时,一些事务(victim)需要回滚。回滚分为:

  • total rollback: 回滚到事务开始时
  • partial rollback: 回滚直到释放某个其他事务等待的锁

依然会有starvation的可能。

Example

alt text


Multiple Granularity

在数据库中,锁可以加在一条记录的一个属性上,可以加在一整条记录上,可以加在一整张表上,可以加在一个表空间上,这些都是不同的粒度。

  • 细粒度(fine granularity): 并发度较好,但锁的开销较大
  • 粗粒度(coarse granularity): 并发度较差,但锁的开销较小

例如,在下图中,粒度从粗到细依次为数据库→表空间→表→记录。

alt text

在多粒度情况下,只有S锁和X锁是不够的,还需要引入意向锁(intention lock),分为以下三种:

  • intention-shared (IS): 表示当前节点的更低级节点上会使用S锁;通俗来讲,一个事务对某个节点有IS锁,表示该事务需要读某个子节点
  • intention-exclusive (IX): 表示当前节点的更低级节点上会使用S锁或X锁;通俗来讲,一个事务对某个节点有IX锁,表示该事务需要读或写某个子节点
  • shared and intention-exclusive (SIX): 表示当前节点被显式锁定为S模式,但更低级的节点可能使用X锁;通俗来讲,就是S锁+IX锁

alt text

当事务$T_i$要对某个节点$Q$上锁时,需要遵循以下规则:

  • 遵循compatibility matrix
  • 先要从根节点开始锁
  • $T_i$只有对$Q$的父节点加IX锁或IS锁,才能对$Q$加S锁或IS锁
  • $T_i$只有对$Q$的父节点加IX锁或SIX锁,才能对$Q$加X锁、IX锁或SIX锁
  • 一旦之前释放过任何锁,就不能再进行上锁(两阶段)
  • 只有$Q$的子节点都没有$T_i$的锁,才能释放$Q$的锁

Note

上锁从根到叶子,释放锁从叶子到根。


Insert and Delete Operations

对于之前的read和write,都是针对已有的数据加锁;但考虑insert和delete,其涉及数据的存在性变化,如何加锁?

如果要删除一个数据项,需要先加X锁;如果要插入一个数据项,会自动加X锁。

Handling Phantoms:

对于之前的幽灵问题,有如下解决方法:

让表格关联一个额外的数据项,该数据想可以表示表格的元信息。如果一个事务需要扫描表格,则需要先获得该数据项的S锁;如果一个事务需要插入或删除记录,则需要先获得该数据项的X锁。

但是,这样会降低并发度。

Index Locking Protocol:

索引锁定协议也可以解决幽灵问题。

每一张表格至少含有一个索引,一个事务要访问记录,需要根据索引路径寻找,并且对索引叶子节点加S锁;而对于进行插入、更新或删除的事务,需要更新索引,并获得相应索引叶子节点的X锁。

同时,还需要遵守两阶段封锁协议。

Next-Key Locking Protocol:

next-key锁定协议与索引锁定协议不同之处在于,其并不将整个索引叶子节点都上锁,而是以索引项的粒度上锁。

例如,如果第一个叶子节点的值为3, 5, 8, 11, 14,第二个叶子节点的值为18, 24, 38, 55,现在有范围查询[7, 16],那么会对8, 11, 14, 18这四个索引项加S锁。接下来,如果要插入15或者7,都会导致冲突。

评论