lectrue17 时间戳排序并发控制

时间戳排序(T/O):时间戳排序(T/O)是一类乐观的并发控制协议,DBMS 假定事务冲突很少发生。与要求事务在读取/写入数据库对象之前获取锁不同,DBMS 使用时间戳来决定事务的可串行化顺序。

每个事务 Ti 都被分配一个唯一且固定的时间戳 TS(Ti),这些时间戳单调递增。不同方案在事务执行的不同阶段分配时间戳。有些高级方案甚至为单个事务分配多个时间戳。如果 TS(Ti) < TS(Tj),那么 DBMS 必须确保实际执行调度等价于将 Ti 排在 Tj 之前的串行调度。

关于时间戳分配有多种实现策略。DBMS 可以使用系统时钟作为时间戳,但在夏令时等边缘情况下会出现问题。另一种选择是使用逻辑计数器,不过这会带来溢出问题以及在包含多台机器的分布式系统中维护该计数器的难题。也存在将两者结合的混合方法。


基本时间戳排序:基本时间戳排序协议允许对数据库对象进行读写而不使用锁。相反,每个数据库对象 X 都带有两个时间戳:上一次成功对该对象执行读操作的事务时间戳(记为 R-TS(X))和上一次成功写操作的事务时间戳(记为 W-TS(X))。DBMS 在每次操作时检查这些时间戳。如果某事务的访问违反了时间戳顺序,则该事务被中止并重启。其基本假设是冲突很少发生,因此重启也很少见。

读操作:

  • 对于读操作,如果 TS(Ti) < W-TS(X),则这违反了与之前对 X 的写者的时间戳顺序(说明有一个按照时间戳来将更晚的事务已经进行了写,我们不希望读取被"未来"事务写入的内容)。因此,Ti 被中止并以新时间戳重新启动。
  • 否则,读是合法的,允许 Ti 读取 X,并将 R-TS(X) 更新为 max(R-TS(X), TS(Ti))。同时,DBMS 需要在私有工作区中为 Ti 做一份 X 的本地副本以保证可重复读。

写操作:

  • 对于写操作,如果 TS(Ti) < R-TS(X) 或 TS(Ti) < W-TS(X),则 Ti 必须重启(不希望覆写"未来"的改动)。
  • 否则,DBMS 允许 Ti 写 X 并更新 W-TS(X)。同样需要在本地工作区中制作 X 的副本以保证可重复读。

优化------Thomas写入规则:

  • 对写操作的一个优化是:若 TS(Ti) < W-TS(X),DBMS 可以忽略该写入并允许事务继续,而不是中止并重启。这称为 Thomas 写入规则。因为既然有更晚的写已经存在,Ti 的写结果在任何按时间戳的最终状态中都不应出现,故可当作废弃写

一些性质:

  • 如果不使用 Thomas 写入规则,基本 T/O 协议产生的调度是冲突可串行化的。
  • 不会产生死锁,因为没有事务会阻塞等待。
  • 然而,长时间运行的事务更有可能被饿死(因为它们更可能读取到来自更晚事务的对象)。
  • 它也允许产生不可恢复的调度。可恢复的调度要求事务只在其所读的那些事务都已提交后才提交;否则在崩溃恢复时 DBMS 无法保证读到的数据能被恢复。

潜在问题:

  • 每次读对象都需写回时间戳。
  • 复制数据到事务工作区和更新时间戳带来高开销。
  • 长事务可能会被饿死。
  • 在高度并发的系统中存在时间戳分配的瓶颈。
  • 允许产生不可恢复的调度。

乐观并发控制(OCC):乐观并发控制(OCC)是另一种乐观并发控制协议,也使用时间戳来验证事务。OCC 在冲突较少时效果最好,比如所有事务大多是只读或事务访问的数据子集互不重叠的情况。如果数据库很大且负载不偏斜,那么冲突概率较低,OCC 是一个不错的选择。

在 OCC 中,DBMS 为每个事务创建一个私有工作区。事务的所有修改都先应用到该工作区。任何被读取的对象都会被复制到工作区,任何被写入的对象也会被复制到工作区并在那里修改。其他事务不能读取某事务私有工作区中的未公布修改。

当事务提交时,DBMS 会比较该事务工作区的写集合,检查是否与其他事务冲突。如果没有冲突,则将写集合安装到全局数据库中。

OCC包含三个阶段:

  1. 读阶段:DBMS 跟踪事务的读/写集合,并将写入保存在私有工作区中。
  2. 验证阶段:当事务要提交时,DBMS 检查它是否与其他事务冲突。
  3. 写阶段:如果验证通过,DBMS 将私有工作区的更改应用到数据库;否则中止并重启事务。

验证阶段:DBMS 在事务进入验证阶段时分配时间戳。为了只允许可串行化的调度,DBMS 检查正在提交的事务 Ti 与其他事务之间的读-写(RW)和写-写(WW)冲突,并确保所有冲突单向发生。

  • 方法 1:向后验证(从较晚的事务到较早的事务)
  • 方法 2:向前验证(从较早的事务到较晚的事务)

这里描述向前验证的工作方式。DBMS 将要提交事务的时间戳与所有其他正在运行的事务进行检查。尚未进入验证阶段的事务被赋予时间戳 ∞(无穷大)。

如果 TS(Ti) < TS(Tj),则下面三种情况之一必须成立:

  1. Ti 在 Tj 开始执行之前完成了所有三个阶段(串行顺序)。
  2. Ti 在 Tj 开始其写阶段之前完成,并且 Ti 不向 Tj 读取的任何对象写入。Tj在读的时候,Ti有可能在读,也有可能在写,如果写的话,显然不能向Tj读取的对象写入。
    • WriteSet(Ti) ∩ ReadSet(Tj) = ∅。
  3. Ti 在 Tj 完成其读阶段之前完成其读阶段,并且 Ti 不向 Tj 读取或写入的任何对象写入。此时Ti完成其读阶段时进入写阶段时,Tj仍在读,或者也在写。
    • WriteSet(Ti) ∩ ReadSet(Tj) = ∅,且 WriteSet(Ti) ∩ WriteSet(Tj) = ∅。

潜在问题:

  • 将数据复制到事务私有工作区带来高开销。
  • 验证/写阶段可能成为瓶颈。
  • 事务在执行完毕后才可能被中止,因而中止代价可能比其他协议更大。
  • 存在时间戳分配的瓶颈。

动态数据库和幻影问题:在之前的讨论中,我们考虑的是在数据库内对一组静态对象进行操作的事务。然而,当事务执行插入、更新和删除时,会出现一系列新的复杂性。幻影问题出现于事务只对已有记录加锁,却忽略了那些正在被创建的记录。这种疏漏会导致不可串行化的执行,因为数据库中的对象集合不再是固定的。

注:前面的三种情况本身是充分的并发安全条件,但它们要求的交集检测是按照谓词/语义范围来做的,而不是按已读到的具体行ID来做。而简单的OCC通常只比较具体ID(例如ReadSet就是事务T1已读的行集合,但当另一个事务T2插入一个新的满足谓词条件的元组时,这个元组并不会自动插入到事务T1的ReadSet中,除非T1重新执行扫描或采用其他策略避免幻影问题)。因此并不会检测到"有新行插入满足谓词"的情形,从而漏掉幻影冲突。

解决幻影问题的方法:

  1. 重新执行扫描:事务可在提交时重新运行查询以检查结果是否发生变化,从而发现由于新记录插入或删除导致的遗漏。缺点是开销大,可能称为瓶颈。
  2. 谓词锁:在验证阶段把读谓词视为防护对象,检查是否有写入与这些谓词重叠(例如插入落入读取的范围)。
  3. 索引锁定:利用索引结构在读时记录受保护的键范围,通过确保没有新数据落入被锁定的范围来防止幻影。

重新执行扫描:DBMS 跟踪事务执行的所有查询的 WHERE 子句。在提交时重新执行这些扫描以确保结果保持一致。

谓词锁:谓词锁最初在 System R 中提出,但并未被广泛实现。不过像 HyPer 这样的系统使用了一种类似谓词锁的精确锁定形式。

索引锁定方案:为防止幻影,使用索引锁定的不同方案包括:

  • 键值锁(Key-Value Locks):对索引中的单个键值加锁,包括表示不存在值的虚拟键。
  • 间隙锁(Gap Locks):锁定键值之后的空隙,防止在这些空隙中插入新记录。
  • 键范围锁(Key-Range Locks):锁定从一个已有键到下一个已有键之间的范围。
  • 分层锁定(Hierarchical Locking):允许事务以不同模式持有更广的键范围锁,从而降低锁管理器的开销。

如果没有合适的索引,事务必须锁定表中的每一页或整个表,以防止可能导致幻影的更改。


隔离级别:可序列化很有用,因为它允许程序员不必关心并发问题,但强制执行可序列化可能会显著限制并行性和性能。因此我们有时会使用较弱的一致性级别以提高可扩展性。

隔离级别控制事务在多大程度上会暴露于其他并发事务的行为之下。

异常类型:

  • 脏读(Dirty Read):读取了未提交的数据。
  • 不可重复读(Unrepeatable Reads):重做一次读操作得到不同的结果。
  • 幻影读(Phantom Reads):插入或删除导致对相同范围扫描的结果不同。

隔离级别(从强到弱):

  1. SERIALIZABLE(可序列化):无幻影,所有读取可重复,且无脏读。
    • 可能的实现:索引锁 + 严格两段锁(Strict 2PL)。
  2. REPEATABLE READS(可重复读):可能发生幻影。
    • 可能的实现:严格两段锁(Strict 2PL)。
  3. READ-COMMITTED(已提交读):可能发生幻影和不可重复读。
    • 可能的实现:对独占锁使用严格两段锁,对共享锁在读后立即释放(读后立即释放共享锁)。
  4. READ-UNCOMMITTED(未提交读):所有异常都可能发生。
    • 可能的实现:对独占锁使用严格两段锁,读操作不使用共享锁。

SQL-92 标准中定义的隔离级别只关注在基于 2PL 的 DBMS 中可能出现的异常。除此之外还有两个额外的隔离级别:

  1. CURSOR STABILITY(光标稳定)

    • 介于可重复读与已提交读之间。
    • 防止丢失更新(Lost Update)异常。
    • IBM DB2 的默认隔离级别。
  2. SNAPSHOT ISOLATION(快照隔离)

    • 保证事务中所有读操作都看到事务开始时的一个一致性快照。
    • 事务只有在其写入与自该快照以来的并发更新不冲突时才会提交。
    • 易受写偏差(write skew)异常影响。
相关推荐
xyzhan2 小时前
SQL Server - 列出数据库中所有固定长度字段
数据库·sql·oracle·sql server
skywalk81632 小时前
新华字典:pwxcoo/chinese-xinhua 中华新华字典数据库。包括歇后语,成语,词语,汉字。
数据库·字典
念越2 小时前
MySQL 聚合函数与分组查询全解析
数据库·mysql
知识分享小能手2 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 数据库的备份与恢复 — 语法知识点及使用方法详解(19)
数据库·学习·sqlserver
倔强的石头1062 小时前
一卡通核心交易平台的国产数据库实践解析:架构、迁移与高可用落地
数据库·架构·kingbase
GDAL2 小时前
SQLite 核心特性与应用实战教程:轻量却不简单的嵌入式数据库
数据库·sqlite
源码获取_wx:Fegn08952 小时前
计算机毕业设计|基于springboot + vue家政服务平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
空空潍2 小时前
Redis点评实战篇-关注推送
java·数据库·redis·缓存
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue社区智慧消防管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计