Skip to content

算法Algorithm

乐观并发验证

Optimistic concurrency control · OCC · 串行提交验证

以固定快照和私有写集执行事务,在一个原子步骤内验证所有外部读再发布,证明按提交顺序的串行见证并暴露拆开验证的竞态。

形式陈述 ​

执行时先保留结果,提交时再决定 ​

乐观并发控制允许事务先读和计算,在提交前验证这段工作是否仍可接受。本页选择一种完整但保守的版本:MVCC提供固定开始快照,所有提交共用一个验证并发布的原子入口。目标是让已接受事务按提交顺序构成串行见证,不是声称所有OCC都只能采用此实现。[1, §§3.1、4]

单节点有有限键域,每个键保存已提交版本链,包括缺失版本 ⊥。开始和提交尝试占用严格递增的边界序号,普通读写夹在这些边界之间;事务T开始时记 s(T),普通外部读选择提交序号小于 s(T) 的最新版本。私有写表 WT 优先于快照读;外部读集合 RT记录每个确实从数据库快照取得的键及来源版本。先外部读后再写的键仍留在R中;直接盲写后只读自己的值,不新增外部读依赖。

记 ℓ(x) 为键x最后已提交修改的事件序号。T完成计算后,原子执行:

  1. 检查每个 x∈RT 是否满足 ℓ(x)<s(T)
  2. 只要一项不满足,中止T,丢弃私有写和尚未发布的返回值,并报告键、旧来源及新写者
  3. 全部满足时,分配提交序号c,把 WT全部新版本一起发布,令相应 ℓ(x)=c,最后确认提交

所有比较与发布必须处于同一个串行化入口中。另一个事务不能在“检查通过”与“写入生效”之间发布数据。失败事务没有公共版本,成功事务的所有键同时进入下一份可见状态。

这里假定键和值的访问、内存生命周期与提交原子性由基础设施保证,不展示latch、持久日志或分布式提交。快照版本不能在仍被事务使用时回收。事务提交前的计算只修改私有状态,外部邮件、支付或不可撤销输出不能藏在可重试的计算阶段。

直觉

读集是一份尚待兑现的依据 ​

事务可以基于旧快照工作;验证时,它逐项问“我用过的外部依据,自开始之后有没有人发布过新版本?”若没有,它的每个读在此刻仍会看到同一个来源,刚才算出的结果就能直接放在已提交事务之后。

只看数值相等不够。例如x先从0变1再变0,旧读与当前数值相同,但版本历史已改变。用最后修改序号保留这份区别,也避免把任意数值相等当作应用语义可交换的证明。本协议可能因此多中止,但不会因值恰好回来而漏过已发生的读写依赖。

按提交顺序归纳正确性 ​

假设前m笔已提交事务已有相同的串行解释,当前数据库就是这m笔按提交顺序运行后的结果。第m+1笔T验证成功,说明R中每个键从开始到现在都没有新版本,所以当前公共状态在这些键上与T的快照相同,包括值为缺失的键。

现在把T放到这个串行前缀末尾重新运行。它每次外部读得到原来源,读取自身写也由相同程序次序给出,因此所有条件分支、私有写和返回值不变。盲写不依赖被覆盖的旧值,可以直接成为新的最终版本。原子发布之后,归纳不变量扩展到m+1。

证明同时覆盖只读事务:返回值也要等验证成功才交给调用者。若某应用允许长只读查询在更早的串行位置解释,本规则会显得保守;那需要更宽的验证目标,不能在同一实现中悄悄免掉检查。

例子与边界

两个旧决定,只接受能接在当前状态后的一个 ​

初态x=y=1。A与B都读x、y,若另一个仍为1,就分别准备x=0与y=0。设A先进入提交入口,尚无更新,验证成功并发布x。B随后检查x,发现最后修改序号大于自己的开始序号,必须中止,不能发布y=0。

B重试时建立新快照,看到x=0,于是不再关闭y。它需要重新读、重新判断;只把旧私有写y=0再次提交,会把已经失败的计算当成新事务,失去归纳证明。这个例子对照SI时,两写集不同,SI的写写检查本来会接受两者,正是两种验证目标的差别。

拆开的验证证书会过期 ​

若实现让A验证通过后暂不发布,又让B对同一旧公共状态验证通过,然后A、B各自写入,最终x=y=0。两次检查各自执行时都返回真,合在一起仍不安全。验证成功不是可长期持有的许可证;不把它与发布原子连接,后续写会使证书失效。

并行验证可以有正确实现,例如增加版本重检、写集合保留或其他同步,但这些步骤必须重新证明。不能删除本页原子入口后继续引用原证明。

并发盲写为何可以都提交 ​

若C、D不读x,只分别盲写x=7和x=9,二者R均为空。按C、D提交顺序,两者都可以接受,最终x=9,串行见证就是C、D。若改成经典SI,两个重叠事务写同一键,first-committer-wins会拒绝一个。

所以本页采用快照读取,不意味着它也采用SI的全部提交规则。OCC直接选择提交顺序并验证读依据;SI保留另一组历史限制。若写值来自读x后加一,x便必须进入R,再也不是这个盲写分支。

空查询也是外部读依据 ​

固定键域中,查询[10,13)是否为空,应把10、11、12的状态都加入R,即使结果没有返回任何记录。并发事务插入11后,验证会看见11的新版本并拒绝旧的“为空”判断。若只把返回行加入R,两笔分别插11、12的事务都会拥有空读集,非法双插入就重现了。

单节点有限域只是可复算模型。实际范围验证可以维护范围版本、索引修改证据或谓词读集合,但必须保证插入、删除及旧新键位置都落在验证覆盖中。不能只增加验证频率而继续漏掉对象。

推论与应用

最后修改序号与历史写集等价 ​

朴素串行验证可检查所有自T开始后已提交事务U,要求 WU∩RT=∅。本页的 ℓ(x)<s(T)与这一条件等价:只要任何后续提交改过x,最后修改序号就一定在开始之后;没有后续修改时,它仍在快照之前。

因此在固定键域、已维护最后修改序号的前提下,一次核心验证并发布需 O(1+|RT|+|WT|) 时间,事务状态为 O(1+|RT|+|WT|)。这没有计快照版本链本身。附件为直接展示来源,读一次键x会从尾部逐项寻找可见版本,最坏需 O(1+vx),其中 vx为该键保留版本数;快照选择可以另用索引优化。

附件还保留完整事务trace、版本和事后图检查,这些审计开销不能算作常数提交成本。若最后修改计数器溢出或允许回退,时间比较就不再可靠;若旧版本过早回收,读阶段本身已经不符合快照合同。

乐观不等于无等待或无饥饿 ​

长读事务可能反复被新的短写事务使验证失败。无冲突时容易成功,并不保证每笔事务都在有限次重试内结束。退避、退化为锁式执行或资源保留可以改善进展,但本页不从串行安全性推导公平性。

快照验证终结任务保留每次拒绝的键与两个版本身份,并把写偏差、空区间、并发盲写和错误双验证放在同一入口中复算。与只报告“提交失败”相比,这份证书能解释失败究竟保护了哪一个外部读。

参考资料

[1] H. T. Kung、John T. Robinson,On Optimistic Methods for Concurrency Control,ACM TODS 6(2),1981,pp.213–226,§3.1三种验证条件,§4 pp.220–222串行验证。本文采用其串行验证发布思想,加上明确的固定快照与逐键最后修改序号;不是原文并行验证分支的完整实现。

关系图谱5 个相邻概念 · 1 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组