“如果R只读x、不读y,删去Q→R,余下只有R→P→Q,拓扑序R、P、Q成为合法串行见证。提交的现实先后是Q、R、P,却可以有不同的串行解释。只看到两条连续rw边,不能宣称已经找到环;SSI正…”
形式陈述
在SI上增加拒绝证书
快照隔离让事务读开始时的已提交视图,并以first-committer-wins排除重叠事务对同键的双提交。可串行化快照隔离(SSI)保留这些基础规则,再记录读写反依赖,拒绝可能形成串行矛盾的事务。读者不会因这个逻辑读标记而把写者挡在共享锁之外;但提交仍可能失败。
本页继续用多版本依赖图的版本、wr/ww/rw与已提交投影。各开始事件s、提交事件c严格排序;T与U并发,指二者生命区间重叠,即
在满足经典SI前提的已提交历史中,每个有向环都包含
两条边各自连接并发事务,并且可以选
这样的双边链称为危险结构;它是环的必要结构,本身不是环的充分条件。一个控制器可以保守地中止链上一笔尚未提交事务,即使实际上不存在返回路径。已经确认提交的事务不能事后撤销来补证明。
本页选用可完整复算的提交参考器
为把在线决定做成有限程序,本页保留全部历史,不实现生产系统的压缩读标记或垃圾回收。候选事务T提交时,在同一个原子入口执行:
- 先作SI写写检查;若自s(T)以来已有提交改过T要写的键,直接中止
- 暂把T视为此刻提交,连同既有全部已提交事务重建真实版本图;未完成、已中止的其他事务不加入
- 寻找上述双边链,并要求
、 ;前一个等号只可能是两端同一事务 - 若链的
只读,还要求 。满足这些条件的链作为拒绝证书;存在则中止当前候选T,否则原子发布其私有写并确认提交
这是带提交次序和只读条件的保守SSI参考验证器,不是论文中低开销读写钩子的逐行移植。它选择当前提交者作为受害者,是因为此前已接受的完整历史没有符合条件的危险结构;新出现的证书必涉及当前候选。重新构图时保留过去读过什么,才能发现读者早已提交、写者最后到达的边。
直觉
为什么环里必有相邻两条反依赖
先从SI得到两个时序事实。wr边A→B要求A版本进入B快照,所以
现在选环中最早提交的事务O,前驱为P,再前驱为I。若P与O不并发,P先结束会违背O最早提交,O先结束又会违背P→O要求的
同理,P开始在O提交之前。若I与P不并发,I先结束便早于P开始、从而早于O提交;P先结束又违背I→P的开始/提交约束。于是I、P也并发,另一条边同样是rw。两端I=O时论证仍成立。这就解释了为什么只跟踪并发rw双边链,也能拦住所有环。
为什么只读条件也成立
在刚才选出的环中,如果I只读,其前一条入边不可能是ww或rw,因为两者的终点都会写。因此前驱J通过wr连到I,满足
只读事务在形成危险结构时不一定有问题;但要成为这个真正环里的I,O必须在它取快照之前提交。参考器第4步利用这一必要条件,消除一部分无害链,并未把“只读”直接当成永不出错的标记。[2, §4.1 Theorem 3]
当前提交者为什么总来得及拒绝
对已接受事务数归纳。空历史无危险结构。一次候选若通过,扩展历史仍无符合条件的危险结构,所以由定理也无环;若不通过,它尚未发布,删除这个候选后旧历史原样保留。后来的事务可能给已提交者增加反依赖,但在它自己的提交入口会重新检验,不能让新环越过检查。
这个证明依赖完整历史和提交原子性。只检查候选自己的incoming/outgoing是否都非空,会漏掉候选恰是链端点、另一个已提交事务才是pivot的情况。只保留仍活跃读者,则会漏掉已经结束的只读R指向后提交P的边。
例子与边界
二环中两端可以是同一个名字
A、B都读x=y=1,分别准备x=0、y=0,先让A提交。此时只有A进入已提交图。B尝试提交时,两条反依赖一起可见;取I=A、pivot=B、O=A,O与I是同一事务,且A比B更早提交。参考器拒绝B,A的提交不必被撤销。
若代码错误要求I、pivot、O三个名字全不同,这个最常见的写偏差二环便会逃过检查。另一方面,若两个事务写同一键,它们应先被SI写写规则处理,不能在危险结构阶段才临时补回被省掉的SI前提。
只读三环与无环误拒绝用同一张时间表
P在事件1开始,读y=0;Q在2开始,写y=1并在3提交;R在4开始,读取x=0及Q的y=1,在5提交;P最后准备在6发布x=1。P提交前旧图只有Q→wr R;加入P后出现R→rw P→rw Q,并由Q→wr R闭环。
危险结构取I=R、pivot=P、O=Q。Q的提交3早于R快照4,也早于R、P的提交5、6,两个时序过滤都不能排除它。虽然当前候选P是pivot,参考器的正确性不依赖每次都碰巧如此。
若R只读x、不读y,时间不变,两条rw依旧成立,Q→R却消失。此时R、P、Q是合法串行顺序,但同一保守参考器仍会拒绝P。这个反例把“结构检测成功”与“准确证明已有环”分开;若要减少这次误拒绝,可以做完整图环检测,却会采用不同的检测成本和状态合同。
快照刚建立时没有冲突,不代表已经安全
只读I若要完全免除SSI跟踪,需要知道没有与它并发的更新事务P,可能成功提交并带有指向快照前已提交O的rw边。P在I开始时尚未完成,未来还可能读取旧版本而产生这条边;仅看当前两个布尔标记都为假,不够证明安全。[2, §§4.2–4.3]
一个容易核实的充分入口是:事务提前声明只读、后续接口禁止写,且建立快照时没有任何活跃更新事务。不能让一次“尚未写”的普通事务借此豁免后再转成写事务。以后新开始的更新者都能看见该快照之前的已提交版本,不可能再形成指向更早O的相应rw边;已结束的更新者又不与只读者并发。因此该快照不会参与SI串行异常。前提还包括更新事务本身受同一SSI规则控制,不能让不参与协议的写入绕过依赖记录。
更一般的安全快照可以等待开始时并发更新者的结局后判定;等待的是安全证明,不是随便睡固定毫秒。本文附件只核这些明确时序,不实现实际数据库的deferrable事务或有限内存回收策略。
推论与应用
读标记不是阻塞式共享锁
论文实现可在读时记录非阻塞的SIREAD标记,写者遇到它时记录反依赖而不等待该读者释放;读旧版本时也要检查已有较新写者。两种发现次序都不可少。读者提交后,标记还可能被并发、较晚提交的写者需要,不能按普通共享锁的提交释放规则直接清空。[3, §3.1]
本页完整历史参考器通过重建两种时序的边规避这项内存工程,但状态随历史增长,没有恒定空间保证。设候选及已提交事务数为n,版本数V、外部读条目数R;构图粗略需
重试与协议范围
被拒绝的事务必须重新取得快照并重跑全部业务计算。已经确认提交的事务保持原决定;不能撤销用户可见结果来“修图”。若同一拒绝证书的其他事务已全部完成,新开始的重试与它们不再并发,不会再由那一组并发边得到同一证书;新的并发事务仍可能造成另一轮失败,故没有确定重试次数或无饥饿结论。
谓词查询必须覆盖潜在插入、删除和条件变化。固定键域附件把范围展开成包含缺失状态的逐键读;真实系统用非阻塞范围证据或等价机制。仅跟踪返回行、允许脏来源,或者不执行SI写写规则,都会使本页的危险结构定理前提失效。
快照验证终结任务对同一输入分别运行SI、OCC与SSI,报告两条rw的键、并发区间、O最早提交及只读过滤结果,再把误拒绝例交给完整图拓扑序核对。安全证明、保守性和实现成本由三份不同证据负责。
参考资料
[1] Alan Fekete等,Making Snapshot Isolation Serializable,ACM TODS,2005,§2.2 Theorem 2.1,p.506。原证明选环最早提交者,再分析两个前驱;本页沿该原理完整重述,不把静态程序图与实际事务图混用。
[2] Dan R. K. Ports、Kevin Grittner,Serializable Snapshot Isolation in PostgreSQL,PVLDB 5(12),2012,§3.2–3.3.1、§4.1–4.3,pp.1853–1855。采用提交次序与只读条件、安全快照定义;没有把该历史论文写作当前产品配置指南。
[3] Michael J. Cahill、Uwe Röhm、Alan D. Fekete,Serializable Isolation for Snapshot Databases,SIGMOD 2008,§2.2、§3.1,pp.731–734。采用并发反依赖和非阻塞读证据;附件为完整历史的提交参考器,不宣称复现其优化实现或实验结果。