数据库系统——并发控制的三种协议
一、基于锁的协议
锁机制通过控制对数据项的访问来避免并发事务之间的干扰,确保事务的隔离性。以下是对基于锁的协议的详细介绍:
1. 基本定义
-
锁(Lock):锁是一种机制,用于管理对数据库资源(如数据项、数据页等)的并发访问。锁可以分为多种类型,最常见的是共享锁(Shared Lock, S 锁)和排他锁(Exclusive Lock, X 锁)。
-
共享锁(S 锁):允许多个事务同时读取数据项,但不允许修改。一旦某个事务对数据项加了共享锁,其他事务也可以加共享锁,但不能加排他锁。
-
排他锁(X 锁):允许事务对数据项进行读写操作。一旦某个事务对数据项加了排他锁,其他事务不能对该数据项加任何类型的锁。
2. 协议内容
基于锁的协议主要有两种:两阶段锁协议(Two-Phase Locking, 2PL)和严格两阶段锁协议(Strict Two-Phase Locking, S2PL)。
2.1 两阶段锁协议(2PL)
两阶段锁协议规定事务分为两个阶段:扩展阶段(Growing Phase)和收缩阶段(Shrinking Phase)。
- 扩展阶段:事务可以请求并获得任何类型的锁,但不能释放任何锁。
- 收缩阶段:事务可以释放锁,但不能再请求任何新的锁。
这种协议确保了冲突可串行化,但是并不能保证不会发生死锁。
2.2 严格两阶段锁协议(S2PL)
严格两阶段锁协议是两阶段锁协议的一个严格版本,它规定:
- 所有的排他锁(X 锁)必须在事务提交或回滚之后才能释放。
这种协议通过确保所有的写操作在事务完全结束前对其他事务不可见,保证了更高的隔离性和一致性,同时可以避免级联回滚,通常用于实现串行化调度。
3. 举例说明
例子:两阶段锁协议(2PL)
假设有两个事务 T1 和 T2,它们分别对数据项 A 和 B 进行操作。
- T1:读取 A -> 修改 A -> 读取 B -> 修改 B
- T2:读取 B -> 修改 B -> 读取 A -> 修改 A
按照两阶段锁协议的执行过程:
- T1 请求并获得对 A 的共享锁(S 锁),读取 A。
- T1 升级对 A 的锁为排他锁(X 锁),修改 A。
- T2 请求并获得对 B 的共享锁(S 锁),读取 B。
- T2 升级对 B 的锁为排他锁(X 锁),修改 B。
- T1 请求并获得对 B 的共享锁(S 锁),读取 B。(此时 T2 已经持有 B 的排他锁,T1 需要等待)
- T2 请求并获得对 A 的共享锁(S 锁),读取 A。(此时 T1 已经持有 A 的排他锁,T2 需要等待)
这种情况下可能会出现死锁。
例子:严格两阶段锁协议(S2PL)
假设有两个事务 T1 和 T2,它们分别对数据项 A 和 B 进行操作。
- T1:读取 A -> 修改 A -> 读取 B -> 修改 B
- T2:读取 B -> 修改 B -> 读取 A -> 修改 A
按照严格两阶段锁协议的执行过程:
- T1 请求并获得对 A 的排他锁(X 锁),读取和修改 A。
- T1 请求并获得对 B 的排他锁(X 锁),读取和修改 B。
- T1 提交事务,释放对 A 和 B 的排他锁。
- T2 请求并获得对 B 的排他锁(X 锁),读取和修改 B。
- T2 请求并获得对 A 的排他锁(X 锁),读取和修改 A。
- T2 提交事务,释放对 A 和 B 的排他锁。
这种情况下,不会出现死锁,因为事务 T1 在它结束之前不会释放任何排他锁。
4. 总结
基于锁的协议通过管理事务对数据项的访问,确保并发事务之间的隔离性和一致性。两阶段锁协议和严格两阶段锁协议是两种主要的锁协议,它们在实际应用中广泛使用,分别适用于不同的并发控制需求。
二、基于时间戳的协议
与基于锁的协议不同,时间戳协议通过时间戳的比较来决定事务的执行顺序,从而避免冲突和确保一致性。以下是对基于时间戳的协议的详细介绍:
1. 基本定义
-
时间戳(Timestamp):每个事务在开始时都被分配一个唯一的时间戳,通常是事务开始的时间。时间戳可以用来全局地排序事务,从而决定事务的执行顺序。时间戳通常由系统时钟生成,或者通过计数器递增。
-
读时间戳(Read Timestamp, RTS):每个数据项 A 维护一个读时间戳 RTS(A),表示最后一次成功读取该数据项的事务的时间戳。
-
写时间戳(Write Timestamp, WTS):每个数据项 A 维护一个写时间戳 WTS(A),表示最后一次成功写入该数据项的事务的时间戳。
2. 协议内容
基于时间戳的并发控制协议主要有两种:基本时间戳排序协议(Basic Timestamp Ordering, BTO)和改进的时间戳排序协议。
2.1 基本时间戳排序协议(BTO)
基本时间戳排序协议通过检查事务的时间戳来决定读写操作的合法性:
-
读操作(事务 T 对数据项 A 的读取操作 Read(A)):
- 如果 T 的时间戳 TS(T) < WTS(A),则事务 T 需要读取的数据已经被一个更新的事务修改过,这意味着读操作是不合法的,事务 T 被回滚并重新启动。
- 否则,读操作是合法的,更新 RTS(A) 为 max(RTS(A), TS(T))。
-
写操作(事务 T 对数据项 A 的写入操作 Write(A)):
- 如果 T 的时间戳 TS(T) < RTS(A),则事务 T 试图写入的数据已经被一个更新的事务读取过,这意味着写操作是不合法的,事务 T 被回滚并重新启动。
- 如果 T 的时间戳 TS(T) < WTS(A),则事务 T 试图写入的数据已经被一个更新的事务修改过,这意味着写操作是不合法的,事务 T 被回滚并重新启动。
- 否则,写操作是合法的,更新 WTS(A) 为 TS(T)。
2.2 改进的时间戳排序协议
为了减少事务回滚的可能性,改进的时间戳排序协议引入了延迟写入(Thomas' Write Rule):
- 延迟写入规则:如果事务 T 的写操作 Write(A) 不合法,即 TS(T) < WTS(A),则该写操作被忽略而不是使事务回滚。这种情况下,事务 T 继续执行而不影响其他事务。
3. 举例说明
例子:基本时间戳排序协议(BTO)
假设有两个事务 T1 和 T2,它们的时间戳分别为 TS(T1) = 1 和 TS(T2) = 2,并且它们对数据项 A 进行操作。
- 初始状态:RTS(A) = 0, WTS(A) = 0
- T1 读取 A(Read(A)):
- TS(T1) = 1 > WTS(A) = 0,读操作合法,更新 RTS(A) 为 1。
- T2 写入 A(Write(A)):
- TS(T2) = 2 > RTS(A) = 1,TS(T2) = 2 > WTS(A) = 0,写操作合法,更新 WTS(A) 为 2。
- T1 写入 A(Write(A)):
- TS(T1) = 1 < WTS(A) = 2,写操作不合法,事务 T1 回滚并重新启动。
例子:改进的时间戳排序协议(延迟写入规则)
假设有两个事务 T1 和 T2,它们的时间戳分别为 TS(T1) = 1 和 TS(T2) = 2,并且它们对数据项 A 进行操作。
- 初始状态:RTS(A) = 0, WTS(A) = 0
- T1 读取 A(Read(A)):
- TS(T1) = 1 > WTS(A) = 0,读操作合法,更新 RTS(A) 为 1。
- T2 写入 A(Write(A)):
- TS(T2) = 2 > RTS(A) = 1,TS(T2) = 2 > WTS(A) = 0,写操作合法,更新 WTS(A) 为 2。
- T1 写入 A(Write(A)):
- TS(T1) = 1 < WTS(A) = 2,写操作不合法,根据延迟写入规则,忽略 T1 的写操作而不回滚事务。
4. 总结
基于时间戳的并发控制协议通过时间戳机制来决定事务的执行顺序,确保数据库的一致性和隔离性。基本时间戳排序协议通过严格的时间戳检查来管理读写操作,但可能导致较多的事务回滚。改进的时间戳排序协议通过延迟写入规则减少了事务回滚的次数,提高了系统的并发性能和效率。这些协议在实际应用中广泛用于需要高并发和严格一致性的数据库系统中。
三、基于有效性检查的协议
数据库并发控制中的基于有效性检查的协议(也称为乐观并发控制协议,Optimistic Concurrency Control, OCC)是另一种并发控制方法。该方法假设大部分事务不会冲突,因此允许事务乐观地执行,最后在提交时检查冲突并处理。以下是对基于有效性检查的协议的详细介绍:
1. 基本定义
基于有效性检查的协议分为三个主要阶段:
- 读取阶段(Read Phase):事务 T 读取数据库中的数据项并将它们保存在本地的工作空间中。此阶段不会对数据库进行任何更改。
- 有效性检查阶段(Validation Phase):事务 T 准备提交时,系统会检查 T 是否与其他并发事务冲突。如果检测到冲突,事务会被回滚;否则,进入下一个阶段。
- 写入阶段(Write Phase):如果事务通过了有效性检查,系统将事务 T 的所有更改应用到数据库中。
2. 协议内容
基于有效性检查的协议的核心是有效性检查阶段,通过以下几种方法来确保事务的隔离性和一致性:
2.1 有效性检查条件
假设有两个事务 T1, T2 ,它们分别有自己的读取集(Read Set, RS)和写入集(Write Set, WS)。有效性检查的基本条件如下:
- 条件 1:事务 T1 在事务 T2 开始之前结束,即:
TS(T1) < TE(T1) < TS(T2)
- 这种情况下,事务 T1 和 T2 不会冲突,因为 T1 完全在 T2 开始之前完成。
- 条件 2:事务 T1 和 T2 (满足TS(T1) < TS(T2)) 之间没有写写冲突和读写冲突,即:
WS(T1) ∩ WS(T2) = ∅
RS(T1) ∩ WS(T2) = ∅
- 表示事务 T1 和 T2 不会同时修改相同的数据项,并且不存在T1在读数据时T2修改数据。
有效性检查的具体实现通常包括以下几种策略:
2.2 基于读写集的有效性检查
-
检查写写冲突:
- 如果两个并发事务的写入集存在交集,则判定为冲突,需要回滚其中一个事务。
-
检查读写冲突:
- 如果事务 T1 的读集与事务 T2 的写入集存在交集,并且 T2 在 T1 提交之前已开始,则判定为冲突,需要回滚其中一个事务。
3. 举例说明
假设有三个事务 T1、T2 和 T3,它们分别在以下时间点进行操作,并有如下的读写集:
-
事务 T1:
- 读取时间 (
TS(T1)
):1 - 提交时间 (
TE(T1)
):4 - 读取集 (
RS(T1)
):{A, B} - 写入集 (
WS(T1)
):{B, C}
- 读取时间 (
-
事务 T2:
- 读取时间 (
TS(T2)
):2 - 提交时间 (
TE(T2)
):5 - 读取集 (
RS(T2)
):{B, C} - 写入集 (
WS(T2)
):{C, D}
- 读取时间 (
-
事务 T3:
- 读取时间 (
TS(T3)
):3 - 提交时间 (
TE(T3)
):6 - 读取集 (
RS(T3)
):{A, D} - 写入集 (
WS(T3)
):{A, B}
- 读取时间 (
有效性检查:
-
T1 和 T2:
- 检查冲突:
RS(T1) ∩ WS(T2) = {A, B} ∩ {C, D} = ∅
,没有读写冲突。 - 检查冲突:
WS(T1) ∩ WS(T2) = {B, C} ∩ {C, D} = {C}
,存在写写冲突。 - 由于写写冲突,需要回滚其中一个事务。假设回滚 T1,重新开始。
- 检查冲突:
-
T2 和 T3:
- 检查冲突:
RS(T2) ∩ WS(T3) = {B, C} ∩ {A, B} = {B}
,存在读写冲突。 - 由于读写冲突,需要回滚其中一个事务。假设回滚 T2,重新开始。
- 检查冲突:
-
T3 和 T1(假设 T1 重新开始后):
- 检查冲突:
RS(T3) ∩ WS(T1) = {A, D} ∩ {B, C} = ∅
,没有读写冲突。 - 检查冲突:
WS(T3) ∩ WS(T1) = {A, B} ∩ {B, C} = {B}
,存在写写冲突。 - 由于写写冲突,需要回滚其中一个事务。假设回滚 T3,重新开始。
- 检查冲突:
4. 总结
基于有效性检查的协议通过乐观地假设事务不会冲突,允许事务自由地读取和修改数据,直到提交时进行冲突检查。这种方法在冲突较少的环境下具有较高的并发性能,因为大部分操作都在没有锁的情况下执行。然而,在冲突频繁的情况下,事务回滚的开销可能较大,影响系统性能。有效性检查的关键在于设计合理的读写集检查策略,以最小化冲突和回滚的影响。