知识卡片
多重赋值:解决"延迟检查"困境的更优方案
内容
“供应商S1和零件P1必须在同一城市”这类约束,会遇到一个具体的立即检查困境:
如果要把S1和P1都改到某个新城市,两条分开的UPDATE语句在立即检查下,第一条
执行完(改了S1)、第二条还没执行(P1还在旧城市)时,中间状态必然违反约束,
导致第一条UPDATE就失败。传统解决方案是把约束标记为DEFERRABLE,延迟到COMMIT
时刻才检查,让两条UPDATE语句共处同一事务、中间容忍暂时不一致——但这正是
[[ACID一致性不等于正确性及事务不是完整性单位]]和[[数据库约束必须立即检查的
理由与语义优化]]反对的做法:如果两条UPDATE之间去问事务”S1和P1现在在不同
城市吗”,得到的答案会是”是的”,这个不一致完全是真实存在、可被观察到的。
更好的方案是支持多重赋值(multiple assignment):允许任意数量的单独赋值
“同时”作为同一条语句的组成部分执行,比如Tutorial D里用逗号连接两个UPDATE
(S:=...,P:=...;)。多重赋值的语义分两步:先计算等号右边所有源表达式的值,
再统一执行左边所有变量的赋值——因为所有源表达式都是基于赋值前的状态计算的,
各个单独赋值之间互不依赖,执行顺序无关紧要,可以视为并行/同时发生;更关键
的是,因为多重赋值在语义上是一个原子运算,赋值”中途”根本不存在可被观察的
状态,所以完整性检查不会、也不需要在赋值过程中途介入——这样,两条本来分开
会失败的UPDATE,打包成一次多重赋值后就能直接成功,而且完全不需要引入延迟
检查这个机制。SQL对多重赋值有一些零散的隐式支持(级联删除、连接视图更新、
FETCH/SELECT INTO、行赋值),但缺少对”多个不同表同时赋值”这种显式多重赋值
的直接支持,这恰好是绕开延迟检查所必需的那种能力,书中期望这会在SQL标准
未来版本里得到补齐。