“T1年龄10,T2年龄20;两者都已在区间协议中取得S[10,13),并读到空。T1准备插11,T2准备插12。”
形式陈述
锁住的是可能满足条件的对象
一般谓词锁写成(T,P,M):事务T、对潜在记录定义的谓词P、模式M∈{S,X}。两个不同事务的锁冲突,当存在一条可能的记录同时满足两个谓词,且至少一方为X。不是只检查当前表中有没有这样的记录。记录尚未存在时,其未来插入仍可能改变先前查询结论。[1, §3]
本页把这个抽象具体化到有限键域K={0,…,U−1},U为正整数;每键保存一个值或“缺失”。谓词仅为整数半开区间[a,b),满足0≤a≤b≤U。锁保护此区间内各键的存在性和完整值,不含NULL、排序规则或任意用户函数。空区间a=b不保护任何键,可以直接完成。
对两非空区间[a,b)、[c,d),冲突条件为
区间端点是整数,单键k可表示为[k,k+1)。本页的点X锁是逻辑键排他锁;它不采用任何产品的“插入意向锁”额外兼容规则,不能仅凭同名把上述矩阵搬到某个数据库实现。
授锁必须先于观察和修改
事务先取得S[a,b),再扫描整个区间;即使返回零行,也保留这把锁。插入、删除或同键改值前取得X[k,k+1)。把键p改成q时,先取得两个旧新点的X,再原子执行“删p、写q”;取得一半时不能先发布半个改键。读自己的私有写可按事务内部规则处理,其它事务看不到未提交写。
采用严格2PL的终点持有规则。锁管理器在同一原子授锁步骤中检查所有其它事务的不兼容持有锁,再登记新锁;不能先各自查“无冲突”,稍后才各自登记。事务提交按约定原子发布私有写集后释放;中止清理完成后释放。本文不规定日志刷盘或分布式提交。
对每个键k,可把所有覆盖k的S/X锁投影成该键上的普通锁。相交测试保证不同事务不会同时拥有相冲突的投影;先锁后读写和终点持有保证每个投影服从严格2PL。把“缺失”作为合法键状态后,插入就是缺失→存在的一次写,删除是反方向写,故旧2PL的冲突次序证明也包括它们。锁住空查询不需要为每个不存在的键实际分配记录。
直觉
零行答案仍包含信息
“没有记录”常是下一步决策的依据。预订程序先问某个区间有没有占用,再决定插入;若空查询没有留下任何保护,另一程序会得到同一个空答案,两者便可能都执行原本只允许一方执行的动作。
区间锁保存的是这份否定观察的范围。它不会阻止域外操作,也不会强迫两个读者互斥;它只要求可能改变受保护键状态的写者先取得相冲突的权限。
例子与边界
两个不同新键绕过了现存行锁
初始存在键8和16,业务要求[10,13)内至多一条记录。T1、T2都执行“若该区间为空,就插入自己的新键”,T1选择11,T2选择12。只锁查询返回的现存行时,两次查询都返回空,因而两者都没有取得任何行锁;随后各自取得不同键的X,插入11、12并提交。
最终区间有两条记录。两键不同,所以唯一键约束没有冲突;两次点写本身也没有写写竞争。不可串行来自空查询分别依赖另一方尚未插入的状态,而只看现存行的模型漏掉了这种读写依赖。
改为先取S[10,13),两读者可以同时查询为空。T1接着请求X[11,12)被T2的S挡住,T2请求X[12,13)被T1的S挡住。正确保护把非法双提交变成了需要处理的等待环;它没有同时解决进展问题。下一页的wait-die与wound-wait会完成中止、清理和重试。
边界和改键不能漏一端
S[10,13)与X[12,13)冲突,与X[13,14)不冲突;把小于误写成小于等于,会多挡住只在端点相接的区间。S[10,10)为空,不应挡住任何写。
若一行从键9改到11,只锁旧键9不会与S[10,13)相交,却能在受保护查询内制造新成员;只锁新键11则可能漏掉对旧区间读者的保护。应同时覆盖旧键与新键,直到整项修改完成。相同键上的值更新仍取点X,因为本页S锁还保护该值,而不只是成员数量。
一般谓词P若依赖salary等非键字段,一次旧值不满足P、新值满足P的更新同样要被识别为影响P。我们的整数键模型只证明区间这一类;不能拿“旧键仍在原位置”证明任意谓词不会出现新成员。
推论与应用
用固定区间树实现一种具体覆盖
取U=32,把整个键域反复二分直到单键。用多粒度锁给完整落在查询内的节点取S、其祖先取IS。例如[10,13)恰好分成[10,12)与[12,13);不需锁邻近键9或13。写11沿根至[11,12)取得IX意向及叶X,必经过前者;写12必经过后者,因此同样产生冲突。
覆盖节点不能只由当前数据决定,否则空区间又会消失。固定树按键域定义,空叶仍有逻辑身份。这是一种便于核验的实现,不是对动态B+树next-key锁的完整描述;真实索引分裂、范围边界变化和锁转移都需额外协议。
判定成本与保守范围
在本页模型中,两区间重叠只需常数次整数比较。若朴素锁表有L条记录,新请求扫描全部记录,需O(1+L)次检查,表空间O(L)。查询实际读多少数据另计;锁判断为常数不表示整个查询为常数。
在U为二次幂的平衡固定树中,一个连续区间每层至多有两个未完整覆盖的边界节点,因此标准递归覆盖访问O(1+log U)个边界/完整节点。沿路径去重保留祖先,可把本次所需不同节点控制在同阶;若实现对每个覆盖节点重新遍历并重复保存整条祖先路径,则应另计重复工作,不能据图形直观省略它。
任意谓词的相交判定可能很昂贵,甚至超出可判定范围。工程实现可锁一个可计算的更大集合来避免漏冲突,代价是额外阻塞。例如把[10,13)放大为[8,16)仍安全,却也挡住写14。缩小成只锁当前返回键不保守,会漏掉新插入。[1, §3]
终点任务要求先产生无范围保护的错误双提交,再用相同业务程序复算被拒绝的写与重试读值;只验证两条区间是否相交还没有完成事务层证明。
参考资料
[1] Kapali Eswaran、Jim Gray、Raymond Lorie、Irving Traiger,The Notions of Consistency and Predicate Locks in a Database System,Communications of the ACM19(11),1976,§3 Predicate Locks:不存在记录、谓词相交、旧新值覆盖及锁表授予。本文有限键域、半开区间公式与预订程序是教学实例。