基于时间戳的协议
之前已经讲过了调度的概念,SQL 标准规定了四种隔离性级别,在不同的隔离性级别下,我们对调度的要求也不同
但应当注意到,调度一般是由操作系统执行的,数据库系统无法直接控制并行事务的调度方式,因此,数据库系统需要通过一些方法,间接地影响并行事务的调度方式,保证只产生需要的调度,从而实现 SQL 规定的四种隔离性级别
常见的能够间接影响并行事务调度方式的方法有:时间戳、锁、有效性检查、快照隔离等,数据库系统通常用这些方法来实现可串行化的隔离性级别,还有一些其它方法可以用来实现其它隔离性级别
时间戳
基于时间戳的协议,通常会给每个事务分配一个时间戳,时间戳一般是一个整数,不同事务的时间戳一般不同,并且开始时间更晚的事务的时间戳值一般更大
我们有两个很简单的方式给事务分配时间戳:
- 一个事务的时间戳等于这个事务开始执行时,系统时钟的值
- 维护一个计数器,当一个事务开始执行时,将计数器的值当作这个事务的时间戳,之后将计数器值加一
时间戳排序协议
基本规则
时间戳排序协议的目的是,产生的调度都和一个串行调度冲突等价,并且这个串行调度的串行顺序是事务的时间戳顺序
给每个可能被访问到的数据项维护两个值, W − t i m e W-time W−time 和 R − t i m e R-time R−time , W − t i m e ( d ) W-time(d) W−time(d) 维护所有成功执行 w r i t e ( d ) write(d) write(d) 的事务的时间戳最大值, R − t i m e ( d ) R-time(d) R−time(d) 维护所有成功执行 r e a d ( d ) read(d) read(d) 的事务的时间戳最大值
设 t i m e ( T ) time(T) time(T) 表示事务 T T T 的时间戳,当事务 T T T 试图执行 r e a d ( d ) read(d) read(d) 时:
- 若 t i m e ( T ) < W − t i m e ( d ) time(T)<W-time(d) time(T)<W−time(d) ,说明 T T T 需要读的值已经被覆盖,因此操作失败,事务 T T T 回滚
- 若 t i m e ( T ) ≥ W − t i m e ( d ) time(T) \ge W-time(d) time(T)≥W−time(d) ,则可以执行 r e a d ( d ) read(d) read(d) , R − t i m e ( d ) R-time(d) R−time(d) 被设置为 t i m e ( T ) time(T) time(T) 和 R − t i m e ( d ) R-time(d) R−time(d) 的最大值
当事务 T T T 试图执行 w r i t e ( d ) write(d) write(d) 时:
- 若 t i m e ( T ) < R − t i m e ( d ) time(T)<R-time(d) time(T)<R−time(d) ,则事务 T T T 执行 w r i t e ( d ) write(d) write(d) 会导致其它某些事务 r e a d ( d ) read(d) read(d) 读取的值不正确,因此操作失败,事务 T T T 回滚
- 若 t i m e ( T ) < W − t i m e ( d ) time(T)<W-time(d) time(T)<W−time(d) ,则事务 T T T 试图写入的值已过时,因此操作失败,事务 T T T 回滚
- 其它情况下,可以执行 w r i t e ( d ) write(d) write(d) , W − t i m e ( d ) W-time(d) W−time(d) 被设置为 t i m e ( T ) time(T) time(T)
当一个事务因为以上某个原因回滚时,这个事务之后会重启,重启时这个事务将获得一个新的时间戳
根据以上规则和冲突可串行化的定义,可以发现,如果数据库系统采用了时间戳排序协议,就可以保证产生的调度是冲突可串行化的
需要注意的是,仅仅利用以上规则,不能保证产生的调度是可恢复调度,因此时间戳排序协议通常需要一些额外规则,来保证产生的调度是可恢复调度,或者更进一步,是无级联调度,例如,当一个事务 T T T 成功执行了 w r i t e ( d ) write(d) write(d) 后,将 d d d 打上一个未提交标记,当 T T T 提交时,清除该标记,当另一个事务执行 r e a d ( d ) read(d) read(d) 时,若发现 d d d 有未提交标记,则需进行等待,直到未提交标记清除
视图可串行化
对于同一组事务的两个调度 S S S 和 S ′ S' S′ ,如果满足以下条件:
- 如果调度 S S S 中事务 T T T 的一个 r e a d ( d ) read(d) read(d) 操作读取的是 d d d 在调度开始时的初始值,那么调度 S ′ S' S′ 中事务 T T T 的同一个 r e a d ( d ) read(d) read(d) 操作读取的也是 d d d 的初始值
- 如果调度 S S S 中事务 T 1 T_1 T1 的一个 r e a d ( d ) read(d) read(d) 操作读取的是事务 T 2 T_2 T2 的一个 w r i t e ( d ) write(d) write(d) 操作写入的值,那么调度 S ′ S' S′ 中事务 T 1 T_1 T1 的同一个 r e a d ( d ) read(d) read(d) 操作读取的是 T 2 T_2 T2 的同一个 w r i t e ( d ) write(d) write(d) 操作写入的值
- 如果调度 S S S 中最后一个 w r i t e ( d ) write(d) write(d) 操作是由事务 T T T 执行的,那么调度 S ′ S' S′ 中最后一个 w r i t e ( d ) write(d) write(d) 操作也由事务 T T T 执行
那么我们称调度 S S S 和调度 S ′ S' S′ 视图等价
如果一个调度 S S S 和某个串行调度视图等价,则我们称 S S S 是视图可串行化的
我们已经知道冲突可串行化的概念了,设冲突可串行化调度的集合为 P 1 P_1 P1 ,视图可串行化调度的集合为 P 2 P_2 P2 ,等价于某个串行调度的调度的集合为 P 3 P_3 P3 ,则 P 1 P_1 P1 是 P 2 P_2 P2 的子集, P 2 P_2 P2 是 P 3 P_3 P3 的子集
遗憾的是,对于一个调度是否为视图可串行化调度的判定是一个 NP 完全问题,相比于只需要使用拓扑排序就能判定的冲突可串行化调度,视图可串行化调度的判定速度太慢
Thomas 写规则
可以对时间戳排序协议加以改进,从而提高事务的并发度
当事务 T T T 试图执行 w r i t e ( d ) write(d) write(d) 操作时,若 t i m e ( T ) < W − t i m e ( d ) time(T)<W-time(d) time(T)<W−time(d) ,原本的做法是将 T T T 回滚,但事实上,若 R − t i m e ( d ) ≤ t i m e ( T ) < W − t i m e ( d ) R-time(d) \le time(T)<W-time(d) R−time(d)≤time(T)<W−time(d) ,那么事务 T T T 执行 w r i t e ( d ) write(d) write(d) 写入的值,根本不会被任何事务读取到
当另一个事务