ch7.并发控制
在不加干预的情况下,并发执行的事务可能会导致数据库状态的不一致。DBMS的调度器部件,则是对不同事务的各步骤执行顺序进行了一个规范,从而避免不一致性的发生
并发控制便是指保证事务能保持一致性的整个过程
串行调度和可串行化调度
调度(Schedules)
调度是一个或多个事务的重要动作的一个序列,在缓冲区中的数据库元素可以被多个事务读写。例如,下图描述的动作序列就是一个调度(其中, T 1 T_1 T1和 T 2 T_2 T2为事务):
串行调度
如果多个事务的动作之间不发生混合,则这样的调度是串行调度。下图便是一种串行调度:
可串行化调度
可串行化调度S,是可以在效果上与某个串行调度S’相等的
事务语义的影响
为了简化讨论,在此处将会进行如下假设:事务T所写的任意数据库元素A被赋予的值不发生任何算术巧合地依赖于数据库状态。即:不会在可以导致数据库状态不一致的事务T运行后,又能够正好满足一致性(不会出现巧合)
事务和调度的一种记法
在这里,使用如下方式简洁地表示事务和调度:对于事务 T i T_i Ti和数据库元素 X X X,采用 r i ( X ) r_i(X) ri(X)、 w i ( X ) w_i(X) wi(X)来分别表示读、写。例如,对于可串行化调度:
将此调度记为: r 1 ( A ) ; w 1 ( A ) ; r 2 ( A ) ; w 2 ( A ) ; r 1 ( B ) ; w 1 ( B ) ; r 2 ( B ) ; w 2 ( B ) r_1(A);w_1(A);r_2(A);w_2(A);r_1(B);w_1(B);r_2(B);w_2(B) r1(A);w1(A);r2(A);w2(A);r1(B);w1(B);r2(B);w2(B)
冲突可串行化
冲突
冲突是指调度中的一对连续的动作,如果交换了它们的顺序,那么涉及的事务中至少有一个的行为会改变
大多数的动作是不冲突的:
1、
r
i
(
X
)
r_i(X)
ri(X)和
r
j
(
Y
)
r_j(Y)
rj(Y),允许
X
=
Y
X=Y
X=Y。都是读取,不会改变数据库元素的值
2、
r
i
(
X
)
r_i(X)
ri(X)和
w
j
(
Y
)
w_j(Y)
wj(Y),
X
≠
Y
X\neq Y
X=Y。对两个不同的数据库元素X和Y的值进行读写,并不会互相影响
3、
w
i
(
X
)
w_i(X)
wi(X)和
r
j
(
Y
)
r_j(Y)
rj(Y),
X
≠
Y
X\neq Y
X=Y。理由同上
4、
w
i
(
X
)
w_i(X)
wi(X)和
w
j
(
Y
)
w_j(Y)
wj(Y),
X
≠
Y
X\neq Y
X=Y。理由同上
而冲突、不能交换顺序的动作则包括:
1、同一个事务的两个动作,例如:
r
i
(
X
)
r_i(X)
ri(X)和
w
i
(
Y
)
w_i(Y)
wi(Y)。单个事务的动作顺序应该是固定的,不能被重新排列
2、不同事务对同一数据元素的写冲突,例如:
w
i
(
X
)
w_i(X)
wi(X)和
w
j
(
X
)
w_j(X)
wj(X)。X的值在写入的顺序变化后,必然会受到影响
3、不同事务对同一数据库元素的读和写冲突,例如:
r
i
(
X
)
r_i(X)
ri(X)和
w
j
(
X
)
w_j(X)
wj(X)。对同一元素的写入与读取进行交换,必然会产生影响
综上所述,对于不同事务而言,满足以下情况的两个动作不能交换:
1、涉及同一数据库元素
2、至少有一个是写
冲突等价:通过一系列相邻动作的非冲突交换能将它们中的一个转换为另一个
冲突可串行化的调度:一个调度冲突等价于一个串行调度
优先图及冲突可串行化判断
在调度
S
S
S中,如果有
T
1
T_1
T1的动作
A
1
A_1
A1和
T
2
T_2
T2的动作
A
2
A_2
A2,同时满足:
1、在
S
S
S中,
A
1
A_1
A1在
A
2
A_2
A2前
2、
A
1
A_1
A1和
A
2
A_2
A2都涉及同一数据库元素
3、
A
1
A_1
A1和
A
2
A_2
A2中至少有一个是写动作
则称 T 1 T_1 T1优先于 T 2 T_2 T2
这样的定义,正是按照冲突的定义来的,目的是为了能够更好地分析冲突
引入记号:将调度 S S S中的 T 1 T_1 T1优先于 T 2 T_2 T2,简写为 T 1 < S T 2 T_1<_S T_2 T1<ST2
概括先后次序时,可以使用优先图。优先图的结点是调度S中的事务,在结点上使用整数 i i i来表示事务 T i T_i Ti。如果 T i < S T j T_i<_S T_j Ti<STj,则应该有从结点 i i i到结点 j j j的弧
在构造出的优先图中,如果没有环,则说明冲突是可串行化的,只要使用拓扑排序的方式即可构造出冲突等价的串行顺序
使用锁的可串行化实现
锁
调度器通过对锁表的使用,实现冲突可串行化:
使用锁的时候,事务在读写数据库元素之外,还需要申请和释放锁。使用 l i ( X ) l_i(X) li(X)表示事务 T i T_i Ti请求数据库元素 X X X上的锁,使用 u i ( X ) u_i(X) ui(X)表示事务 T i T_i Ti释放它在数据库元素 X X X上的锁
根据事务与调度的相关规则,可以得出锁的规则:
根据事务的一致性:
1、事务只有先前已经在数据库元素上被授予了锁并且还没有释放锁时,才能读或写该数据库元素
2、如果事务封锁某个数据库元素,那么它以后必须为该元素解锁
根据调度的合法性:
任何两个事务都不能封锁同一元素,除非其中一个事务已经释放其锁
这样的简单的锁,可以使调度合法,但不能使调度变得可串行化
封锁调度器
基于封锁的调度器:当且仅当请求将产生合法调度时同意请求。如果请求未被同意,则发出请求的事务被延迟,直到调度器某时刻同意该请求
调度器使用锁表帮助决策
两阶段封锁(two-phase locking, 2PL)
2PL:在每个事务中,所有封锁请求先于所有解锁请求
服从2PL条件的事务被称为2PL事务
有多种锁模式的封锁系统
之前提到的锁实在是太简易了,以至于常常会进行不必要的封锁。为了避免这一点,应该对锁的模式进行区分
共享锁与排他锁
将锁分为共享锁( s l sl sl)与排他锁( x l xl xl)这两类,规定对任何数据库元素X,其上或者可以有一个排他锁,或者没有排他锁而有任意数目的共享锁。这样一来,如果想要写X,就需要有X上的一个排他锁;而如果想对X只读不写,则可以只使用共享锁
根据事务的一致性:如果不是持有排他锁就不能写,并且如果不是持有某个锁就不能读。更精确地说,在任何事务
T
i
T_i
Ti中:
1、读动作
r
i
(
X
)
r_i(X)
ri(X)之前必须有
s
l
i
(
X
)
sl_i(X)
sli(X)或
x
l
i
(
Z
)
xl_i(Z)
xli(Z),而且它们中间没有
u
i
(
Z
)
u_i(Z)
ui(Z)
2、写动作
w
i
(
X
)
w_i(X)
wi(X)之前必须有
x
l
i
(
X
)
xl_i(X)
xli(X),而且它们中间没有
u
i
(
X
)
u_i(X)
ui(X)
根据事务的两阶段封锁:封锁必须在解锁之前。更精确地说,对任意的Y,在任何两阶段封锁事务 T i T_i Ti中,任何 s l i ( X ) sl_i(X) sli(X)或 x l i ( X ) xl_i(X) xli(X)动作前不能有 u j ( Y ) u_j(Y) uj(Y)动作
根据调度的合法性:一个元素或者可以被一个事务排他地封锁,或者可以被几个事务共享地封锁,但不能二者兼而有之。更精确地:
1、如果
x
l
i
(
X
)
xl_i(X)
xli(X)出现在调度中,那么对
j
≠
i
j\neq i
j=i,后面不能再有
x
l
j
(
X
)
xl_j(X)
xlj(X)或
s
l
j
(
X
)
sl_j(X)
slj(X),除非中间间隔了
u
i
(
X
)
u_i(X)
ui(X)
2、如果
s
l
i
(
X
)
sl_i(X)
sli(X)出现在调度中,对于
j
≠
i
j\neq i
j=i,那么后面不能再有
x
l
j
(
X
)
xl_j(X)
xlj(X),除非中间间隔了
u
i
(
X
)
u_i(X)
ui(X)
相容性矩阵
锁的升级
共享锁可以升级为排他锁,但可能会导致死锁。为了避免死锁,引入更新锁
更新锁
引入第三种锁:更新锁 u l ul ul
从而避免死锁问题
更新锁 u l ul ul只给予事务 T i T_i Ti读X而不是写X的权限,但是只有更新锁能在以后升级为写锁,读锁是不能升级的
增量锁
在事务中,增量动作相互之间是可交换的,由此引入增量锁 i l il il:
封锁调度器的一种体系结构
在此处假设:
1、插入锁的动作是调度器的任务
2、事务不释放锁,而是调度器在事务管理器告诉它事务将提交或中止时释放锁
插入锁动作的调度器
锁表部分或全部地位于主存中,但使用的主存通常不是用于查询执行和日志的缓冲池的一部分。锁表由操作系统分配空间
调度器的第I部分会选择适当的封锁方式,而第II部分则会正确地执行第I部分传来的封锁和数据库访问动作序列中的每一个
锁表
锁表可以使用散列表来实现
数据库元素的层次
在对大的数据库元素上锁时,需要更少的锁,但也会使得并发性更低。而对小的数据库元素上锁,需要更多的锁,但并发性也越高
能够上锁的不同数据库元素之间,具有树状结构,例如,关系->块->元组:
警示锁
IS:试图在子元素上获得共享锁
IX:试图在子元素上获得排他锁
警示协议规则:
1、想要放置常规的S或X锁时,需要从层次结构的根开始
2、如果正处于要放置的位置,则不需要查找,直接请求S或X锁
3、如果目标位置还在层次结构的下面,则在这个结点处加上警示锁。当获得许可后,继续处理子结点,这个子结点的子树包含希望上锁的那个结点
例如,对于下述两项事务,一项查询,另一项写入:
会在关系上设置警示锁,而在元组上使用S和X锁:
幻象问题:我们只能对已存在的项目进行上锁,但却很难对当前不存在、即将插入的元素进行上锁
树协议
基于树的封锁的动机
使用标准的2PL时,是几乎不可能并发使用B-树的,因为对于2PL事务而言,B-树的根结点需要在所有的需要锁的数据库元素都获得了锁之后才能解锁
如何在根结点不再需要被锁的时候,对根结点进行释放?使用树协议来解决这个问题
访问树结构数据的规则
以下对锁的限制构成了树协议:
1、事务的第一个锁可以在树的任何结点上(这与之前的第一个锁总在根结点上有所不同)
2、只有事务当前在父结点上持有锁时,才能获得后续的锁
3、结点可以在任何时候解锁
4、事务不能对一个它已经解锁的结点重新上锁,即使它在该结点的父结点上仍持有锁
在此处,假设只有一种锁 l l l,但这种思路可以推广到其他任何封锁方式集合