ACID | 描述 | 使用技术 |
---|---|---|
原子性 | 一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。即事务不可分割、不可约简。 | 并发控制+日志 |
一致性 | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。 | 应用层定义的完整性约束 |
隔离性 | 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。 | 并发控制 |
持久性 | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 | 日志 |
数据库系统(DBMS):一个数据库系统由一个不可再分(indivisible)的、互不重叠(non-overlapping)的数据对象(data objects)的集合构成: { o 1 , o 2 , … , o n } \{o_1,o_2,…,o_n\} { o1,o2,…,on},每一个object都有一个取值范围(domain of values)。这个系统的一个状态(state)就是一个从object到value的映射。数据库的操作有 r e a d ( r ( o i ) ) read(r(o_i)) read(r(oi)) 和 w r i t e ( w ( o i ) ) write(w(o_i)) write(w(oi))。
数据库事务(transaction):一个事务 T i T_i Ti 是一个偏序集 ≺ i \prec_i ≺i:
T i ⊆ { b i } ∪ { r i ( x ) , w i ( x ) ∣ x i s a n o b j e c t } ∪ { a i , c i } . T_i\subseteq \{b_i\}\cup \{r_i(x), w_i(x)\mid x~\mathrm{is~an~object}\}\cup\{a_i, c_i\}. Ti⊆{ bi}∪{ ri(x),wi(x)∣x is an object}∪{ ai,ci}.
其中, a i a_i ai 代表abort; c i c_i ci 代表commit; b i b_i bi 代表begin。 T i T_i Ti 由 b i b_i bi 开始, a i a_i ai 和 c i c_i ci只能存在一个,并且是 T i T_i Ti 的最终操作。为了简洁, b i b_i bi 和 c i c_i ci 经常被省略。
如果操作 o i ( x ) o_i(x) oi(x) 和 o i ’ ( x ) o_i’(x) oi’(x) 属于 T i T_i Ti,那要么 o i ( x ) ≺ i o i ’ ( x ) o_i(x)\prec_i o_i’(x) oi(x)≺ioi’(x),要么 o i ’ ( x ) ≺ i o i ( x ) o_i’(x)\prec_i o_i(x) oi’(x)≺ioi(x)。
T i T_i Ti 中写操作 w i ( x ) w_i(x) wi(x) 写入数据对象 x x x 的值是一个关于 T i T_i Ti 之前(由 ≺ i \prec_i ≺i 定义)所有读取的值的函数。比如在 T 1 : r 1 ( x ) r 1 ( y ) w 1 ( z ) T_1: r_1(x)~r_1(y)~w_1(z) T1:r1(x) r1(y) w1(z) 中, z z z 的值是关于 x x x 和 y y y 的某个函数 f ( x , y ) f(x, y) f(x,y),也就是说我们没有对写操作做任何假设,写入的值可能会取决于 T i T_i Ti 观察到的所有值。
调度(schedule): 调度定义为 S = ( τ , ≺ S ) S=(\tau, \prec_S) S=(τ,≺S),其中:
串行调度(serial schedule):事务按顺序依次执行的调度。
可串行化的(serializable):如果一个调度 S = ( τ , ≺ S ) S=(\tau, \prec_S) S=(τ,≺S) 等价于 某个串行调度 S ’ = ( τ , ≺ S ’ ) S’=(\tau, \prec_{S’}) S’=(τ,≺S’),那么 S S S 就是可串行的。这里”等价于“的概念不同,又衍生出几种可串行的定义:
终态可串行(final state serializability):
我们说两个调度 S 1 S_1 S1 和 S 2 S_2 S2 是终态等价的(final state equivalent),如果它们满足:
我们定义:如果一个调度 S = ( τ , ≺ S ) S=(\tau, \prec_S) S=(τ,≺S) 终态等价于 某个串行调度 S ’ = ( τ , ≺ S ’ ) S’=(\tau, \prec_{S’}) S’=(τ,≺S’),那么 S S S 就是终态可串行化的。在不特别说明的情况下,我们说可串行指的即是终态可串行。
冲突可串行(conflict serializability):
我们说两个调度 S 1 S_1 S1 和 S 2 S_2 S2 是冲突等价的(conflict equivalent),如果它们满足:
我们定义:如果一个调度 S = ( τ , ≺ S ) S=(\tau, \prec_S) S=(τ,≺S) 冲突等价于 某个串行调度 S ’ = ( τ , ≺ S ’ ) S’=(\tau, \prec_{S’}) S’=(τ,≺S’),那么 S S S 就是冲突可串行的。
数据库状态一致性:遵循设计者想要的所有隐含的或声明的约束的数据库状态被称为是一致的。数据库上的操作保持一致性是指,它们将一个一致的数据库状态转换到另一个。
并发控制:并发执行的事务之间的相互影响可能导致数据库状态的不一致,即使各个事务能保持状态的正确性,而且也没有任何故障发生。因此,不同事务各个步骤的执行顺序必须以某种方式进行规范。该规范是由DBMS的调度器部件完成,而保证并发执行的事务能保持一致性的整个过程称为并发控制。
并发事务一致性:多个事务同时访问一个数据库是很正常的。隔离执行的事务是假定能保持数据库一致性的。保证并发操作的事务也保持数据库一致性是调度器的任务。
基于封锁
以下章节介绍了基于封锁的并发控制机制:
基于时间戳
以下章节介绍了基于时间戳的并发控制机制:
基于有效性确认
以下章节介绍了基于有效性确认的并发控制机制:
事务的正确性原则:如果事务在没有其他任何事务和系统错误的情况下执行,并且在它开始时数据库处于一致的状态,那么当事务结束时数据库仍然处于一致的状态。
调度是一个或多个事务的重要动作的一个序列。当研究并发控制时,重要的读写动作发生在主存缓冲区中,而不是磁盘上。也就是说,某个事务T放入缓冲区的数据库元素A在该缓冲区中可能不仅被T还被其它访问A的事务读或写。
如果一个调度的动作组成首先是一个事务的所有动作,然后是另一个事务的所有动作,依次类推,那么这一调度是串行的。不允许动作的混合。通常情况下,数据库终态是与事务执行顺序有关的。
事务T:
,事务U:,X的初始值为25 那么调度(T,U)完成后X在主存中的值为250,而调度(U,T)完成后X在主存中的值为150
事务的正确性原则告诉我们,每个串行调度都将保持数据库状态的一致性。但还有其他能保证可保持一致性的调度。通常,如果存在串行调度S’,使得对于每个数据库初态,调度S和调度S’的效果相同,我们就说这个调度S是可串行化的。
一致的状态可以认为,假设在初始状态有数据库元素A,B满足A=B,那么在任意调度后仍有A=B。
假设在调度完成后,A和B上实施了不同的运算(这里我们忽略了事务的细节),例如:A:=(A+100)*2,B:=(B)*2+100。那么,这样就出现了不一致的情况,这种调度是并发控制机制必须避免的行为类型。
可串行性保证并发执行的事务能保持数据库状态的正确性。
事务语义的影响
仍以上述为例,但将乘2改为加200,那么有A:=(A+100)+200,B:=(B+200)+100。
巧合的是,A和B在实施不同运算之后仍相等。
上述巧合的例子表明调度可串行化与事务的细节是有关系的。不幸的是,**调度器考虑事务所进行计算的细节是不现实的。**由于事务通常不仅包括SQL或其他高级语句书写的代码,还包括通用编程语言编写的代码,不可能确切地说出事务具体在做什么事。但是,调度器的确能看到来自事务的读写请求,于是能够知道每个事务读哪些数据库元素,以及它可能改变哪些元素。通常假定:
Any database element A that a transaction T writes is given a value that depends on the database state in such a way that [no arithmetic coincidences occur].
事务和调度的记法
如果我们假设“没有巧合”,那么**只有事务执行的读和写需要考虑,而不涉及真实的值。**因此,我们可以用
r i ( X ) , w i ( X ) r_i(X),w_i(X) ri(X),wi(X)
分别表示事务 T i T_i Ti读和写数据库元素X。这里我们规定事务 T i T_i Ti是具有下标i的动作序列, T = { T 1 , T 2 , . . . , T k } T=\{T_1,T_2,...,T_k\} T={ T1,T2,...,Tk}为事务的集合。事务集合T的调度S是一个动作序列,其中对T中的每个事务 T i T_i Ti, T i T_i Ti中的动作在S中出现的顺序和其在 T i T_i Ti自身定义中出现的顺序一样。我们说S是组成它的事务动作的一个交错。
对于调度中一对连续的动作,若它们满足:如果它们的顺序交换,那么涉及的事务中至少有一个的行为会改变。那么我们就称这个两个动作是冲突的。
对于两不同事务 T i , T j T_i,T_j Ti,Tj,对以下的两个动作:
动作 | 是否冲突 | 描述 |
---|---|---|
r i ( X ) , r j ( Y ) r_i(X),r_j(Y) ri(X),rj(Y) | 否 | 即使 X = Y X=Y X=Y也不冲突,原因是两事务没有改变数据库元素的值,即不同事务对同一数据库元素的读是不冲突的 |
r i ( X ) , w j ( Y ) , X ≠ Y r_i(X),w_j(Y),X\neq Y ri(X),wj(Y),X=Y | 否 | 不同事务读写不相同的数据库元素不会冲突 |
w i ( X ) , r j ( Y ) , X ≠ Y w_i(X),r_j(Y),X\neq Y wi(X),rj(Y),X=Y | 否 | 理由同上 |
w i ( X ) , w j ( Y ) , X ≠ Y w_i(X),w_j(Y),X\neq Y wi(X),wj(Y),X=Y | 否 | 同一事务写不相同的数据库元素不会冲突 |
r i ( X ) , w i ( Y ) r_i(X),w_i(Y) ri(X),wi(Y) | 是 | 同一事务的两个动作不能交换。因为单个事务的动作顺序是固定的,且不能被重写排列 |
w i ( X ) , w j ( X ) w_i(X),w_j(X) wi(X),wj(X) | 是 | 不同事务对同一数据库元素的写是冲突的。根据“没有巧合”假设,若交换则X最后值可能不同 |
r i ( X ) , w j ( X ) r_i(X),w_j(X) ri(X),wj(X) | 是 | **不同事务对同一数据库元素的读和写冲突。**若交换则 T i T_i Ti读的X值可能不同 |
w i ( X ) , r j ( X ) w_i(X),r_j(X) wi(X),rj(X) | 是 | 理由同上 |
我们得到的结论是,不同事务的任何两个动作可以交换,除以下情况外:
将这一想法进行扩展,我们可以接受任一调度,进行任意非冲突的交换,目标是将该调度转换为一个串行调度。如果我们能做到这一点,那么初始的调度是可串行化的,因为它对数据库状态的影响在我们做每一个非冲突交换时是不变的。我们引申出以下两个概念:
冲突等价:如果通过一系列相邻动作的非冲突交换能将原调度转换为另一个调度,我们说两个调度是冲突等价的。
冲突可串行化:如果一个调度冲突等价于一个串行调度,那么我们说该调度是冲突可串行化的。
冲突可串行化是可串行化的一个充分条件。
对调度S,可串行化指的是如果有一个串行调度S’的执行结果与S相同,那么就认为S是可串行化的;而冲突可串行化是指若S经过若干次非冲突的交换,得到的调度S’‘是串行调度,那么就认为S是冲突可串行化的。这里表明若S是冲突可串行化的,那么就一定存在一个串行调度S’’,且执行结果与S相同,故S也是可串行化的。
为什么冲突可串行化不是可串行化的必要条件?
我们考虑事务T1,T2,T3以及以下两个调度:
S 1 : w 1 ( Y ) ; w 1 ( X ) ; w 2 ( Y ) ; w 2 ( X ) ; w 3 ( X ) ; S 2 : w 1 ( Y ) ; w 2 ( Y ) ; w 1 ( X ) ; w 2 ( X ) ; w 3 ( X ) ; S_1:w_1(Y);w_1(X);w_2(Y);w_2(X);w_3(X);\\ S_2:w_1(Y);w_2(Y);w_1(X);w_2(X);w_3(X); S1:w1(Y);w1(X);w2(Y);w2(X);w3(X);S2:w1(Y);w2(Y);w1(X);w2(X);w3(X);
可以看出S1和S2执行后的结果是相同的,且S1是串行调度。也就是说,调度S2是可串行化的。然而,对数据库元素X或Y,我们发现都无法交换冲突的动作,那么,调度S2是非冲突可串行化的。
检查调度S并决定它是否是冲突可串行化相对而言比较简单。
已知调度S,其中涉及事务T1和T2,可能还有其他事务,我们说T1优先于T2,并记为
T 1 < S T 2 , T_1<_S T_2, T1<ST2,
如果有T1的动作A1和T2的动作A2,满足:
这里正是我们不能交换A1和A2顺序的情况。因此,在任何冲突等价于S的调度中,A1将会出现在A2前。所以,冲突等价的串行调度(若有)必然使T1在T2前。我们可以使用一种记号来概括这种顺序关系,很自然的想到可以用图来表示。
优先图(precedence graph):优先图的结点是调度中的事务。我们用整数i来表示事务Ti。如果 T i < S T j T_i<_S T_j Ti<STj,则有一条从结点i到结点j的弧。
我们可以构造S的优先图,并判断其中是否有环来判断调度S是否冲突可串行化。如果有,那么S不是冲突可串行化的。如果该图是无环的,那么S是冲突可串行化的,而且结点的任何一个拓扑排序都是一个冲突等价的串行顺序。
我们证明如下命题:
若调度S的优先图无环,则S是冲突可串行化的;若有环,则S是非冲突可串行化的。
Proof:
(I) 若有环,不妨设为 T 1 → T 2 → . . . T n → T 1 T_1\rightarrow T_2 \rightarrow ... T_n \rightarrow T_1 T1→T2→...Tn→T1。假设存在冲突可串行化调度(则也存在可串行化调度),那么T1的所有动作应于Tn之前,而 T n → T 1 T_n\rightarrow T_1 Tn→T1表明 T n < S T 1 T_n<_S T_1 Tn<ST1,即Tn中存在动作在T1之前,矛盾。
(II) 若无环,可对事务的个数n用数学归纳法证明。
BASIS:若n=1,即只有一个事务,那么其自然是可串行化的。
INDUCTION:设调度S由以下n个事务组成
T 1 , T 2 , . . . , T n , T_1,T_2,...,T_n, T1,T2,...,Tn,
且S的优先图是无环的。那么,对于有向无环图来说,至少有一个结点没有入边,不失一般性,我们记该结点为 T i T_i Ti。则调度S中不存在这样的动作:不属于事务 T i T_i Ti的、在 T i T_i Ti某个动作之前的且与之冲突的动作。否则,我们就要在该事务与 T i T_i Ti之间加一条指向 T i T_i Ti的弧。
现在,我们可以将调度S划分为
( Actions of T i ) , ( Actions of the other n - 1 transactions ) . (\text{Actions of}\ T_i),(\text{Actions of the other n - 1 transactions}). (Actions of Ti),(Actions of the other n - 1 transactions).
注意到原优先图是无环的,去掉 T i T_i Ti的一条出边仍是无环的。对于剩下的 n − 1 n-1 n−1个事务,由数学归纳法知其是冲突可串行化的,再加上位于动作序列前部的均属于 T i T_i Ti的动作,则证明整个调度S是冲突可串行化的。∎
如果我们去掉T1优于T2的条件3,那么易证在满足该优先条件的优先图中,若优先图无环,则调度S是可串行化的。
证明基于该命题即是平凡的:对任意2个事务T1、T2,若仅存在T1指向T2的弧,则说明T1的动作均在T2之前。
该结论在基于树的封锁协议中应用。
我们考虑调度器最常用的体系结构,这种结构在数据库元素上维护“锁”以防止非可串行化的行为。直观地说,事务获得在它所访问的数据库元素上的锁,以防止其他事务几乎在同一时间访问这些元素并因而引入非可串行化的可能。在本节中,我们用一个简单的封锁模式来介绍封锁的概念,该模式只有一种锁。
调度器的责任是接受来自事务的请求,或者允许它们在数据库上操作,或者将它们推迟直到允许它们继续执行是安全的时候。调度器使用锁表指导决策。
requests from transactions -> Scheduler <--> lock table
|
v
Serializable schedule of actions
调度器转发请求,当且仅当该请求的执行不可能在所有活跃事务提交或中止后使数据库处于不一致的状态。我们称基于封锁(locking)的调度器为封锁调度器(locking scheduler)。封锁调度器像大多数调度器种类一样,事实上实现的是冲突可串行化。
当调度器使用锁时,事务在读写数据库元素以外还必须申请和释放锁。锁的使用必须在两种意义上都是正确的,一种适用于事务的结构,另一种适用于调度的结构。
事务的一致性:动作和锁必须按预期的方式发生联系:
(a) 事务只有先前已经在数据库元素上被授予了锁并且还没有释放锁时,才能读或写该数据库元素。
(b) 如果事务封锁某个数据库元素,它以后必须为该元素解锁。
调度的合法性:锁必须具有其预期的含义:
(a) 任何两个事务都不能封锁同一元素,除非其中一个事务已经先释放其锁。
我们扩展动作的记法:
那么,上述关于锁的表述可以为:
事务的一致性:动作和锁必须按预期的方式发生联系:
(a) 若事务 T i T_i Ti有动作 r i ( X ) r_i(X) ri(X)或 w i ( X ) w_i(X) wi(X),那么前面必须有 l i ( X ) l_i(X) li(X),且二者之间没有 u i ( X ) u_i(X) ui(X)。
(b) l i ( X ) l_i(X) li(X)后面必须有 u i ( X ) u_i(X) ui(X)。
调度的合法性:锁必须具有其预期的含义:
(a) 如果调度在动作 l i ( X ) l_i(X) li(X)之后有 l j ( X ) l_j(X) lj(X),那么两者之间必须有 u i ( X ) u_i(X) u