“列批次的执行合同进一步见批次位置与借用期限;局部选择必须映回共同出现编号。位图堆访问则从多个索引组合候选位置,内存退化到整页时用完整谓词重检;这些接口不改变本页布局与页并集计费。”
形式陈述
一批里究竟包含什么
本页把open/next/close迭代器扩为 next_batch(K),其中 K 是正整数容量。输入为同一固定只读视图下的有限行出现序列 R;每份出现有不同内部编号 i,即使全部列值相同。只讨论不扩张行数的扫描、筛选和逐行投影;连接输出复制与分组状态沿用各自合同,不由“批量”二字自动解决。
一个借用批次 B=(id,e,n,I,C,V,S) 有以下字段:
- id 标识生产者本次执行,e 是本次交付的借用代次;下一次拉取、失败或 close 使上一代次的借用失效
- 0≤n≤K 是本批实际输入槽数;n..K−1 是无效尾部,即使缓冲里还有上次数据也不能读取
- I[0..n) 将局部槽映到输入出现编号;连续扫描时 I[j]=base+j,gather 后必须显式保留映射;在下文序列等式中,I还须保持当前源序列的相对次序,允许有间隔但不允许调换次序
- 每列 C_a[0..K) 与 validity V_a[0..K) 共享同一个 I;V_a[j]=0 表示该列在此行是 NULL,载荷字节本身没有语义
- S 是 0..n−1 内严格递增的选择位置列表;初始为全部有效槽,筛选只删除位置,不按值去重。本文不允许 S 自行复制位置
容量 K、有效长度 n、活跃数 |S| 是三个数。掩码 M[j]=[j<n 且 j∈S] 可以替代 S 表示活跃槽;validity 描述值是否存在,M 描述这份出现是否参加当前运算,二者不能合并。一个有效但 NULL 的槽仍是真实输入出现。
结果标签、生命周期与进度
next_batch 返回互斥标签 DATA(B)、END 或 ERROR(cause)。DATA 可以有 S=[],表示这批没有合格输出;END 才表示后续没有数据。扫描在尚有输入时要求 n>0,每次 DATA 都推进输入游标;筛选可交付空选择,但不能无穷反复返回同一空批。也可以另设计“隐藏空批并继续向下拉取”的适配器,不能把空批直接当 END。
open 后处于 READY;一次成功 DATA 后仍可拉取;END 进入 EXHAUSTED,后续拉取保持 END;ERROR 进入 FAILED,后续拉取保持错误而非 END。close 在任一状态都释放资源并进入 CLOSED,可重复调用;CLOSED 后拉取是接口错误。错误出现前已收到的前缀不构成一份成功完整查询结果,聚合器不得把它作为最终答案。
每次拉取之前调用者须完成上一批的消费,或复制所需的 I、列值、validity 与选择位置到自己的存储。仅复制 B 的外壳或 S 而仍指向旧 C 不够。若实现为零复制页引用,缓冲池pin必须覆盖整个借用期;本页检查器用借用代次检测失效,不声称实现真实页pin或并发安全。
从标量规格到逐批保持
谓词 p 按SQL三值筛选只接纳 TRUE。第一版规格限定 p 和投影 f 纯、确定且在全部合法输入值上总定义;v 列取精确整数,SUM 采用相同的完成约定。这排除除零、越界、溢出、随机值和副作用的可观察差异。
标量计划按出现读取 r_i,若 p(r_i)=TRUE 就输出 (i,f(r_i))。批计划先令 S'=[j∈S:p(r_{I[j]})=TRUE],再只在 S' 的位置计算并输出 (I[j],f(r_{I[j]}))。flatten 按交付次序连接每批的活跃结果,则
证明以已消费出现前缀归纳。初始两边为空;一个新批的 I 恰好覆盖接下来互不重复的输入出现,每个位置接受同一 p 的 TRUE 判定,投影在保留位置取同一列值,所以恰好追加标量端对应片段。空选择追加空序列,END 时没有未消费出现,故等式成立。串联多个筛选/投影时逐算子应用这一不变量。去掉 i 后同值结果仍各有其份数,因此也得到 bag 相等;有限精确整数加法进一步给出相同 SUM。
若gather重新排列出现,只能由同一逐出现论证得到bag等式,不能保留上面的序列等式。这里证明的是所规定顺序的扫描流水线,不是 SQL 无 ORDER BY 的顺序承诺;更换访问路径后只要求 bag 相等。若采用浮点树形归约,结合顺序可改变舍入结果,本页的精确整数结论不能直接搬过去。
直觉
批次像把一段游标停在一张共享工作纸上:列缓冲保存字段,I 保存“这是哪一份行”,S 在纸上圈出本层还要处理的位置。Filter 改的是圈选列表,未必搬动任何列。Project 若在原槽写出新列,后续就仍沿同一 S 读取。
减少的是跨算子、解释器的重复调度。一次函数调用处理四份出现,仍要决定四份各自是否满足条件。向量化接口也不等于硬件 SIMD:普通标量循环可以实现它;SIMD 能否安全、快速执行,还取决于指令、对齐和掩码语义。
图中使用 base=4 的第二批:局部 S=[1,3] 对应全局 I[S]=[5,7],不会错读全局1、3;下半部画出同一缓冲被下一代覆盖时的合法消费路径与失效路径。
例子与边界
十二份出现,五份结果
固定 x=[1,1,0,1,0,1,0,1,1,0,1,0],y=[1,1,1,0,1,1,NULL,1,0,0,1,1],v=[10,10,20,30,40,50,60,70,80,90,100,110]。查询 x=1 AND y=1,返回 v,再求精确整数 SUM。
K=4 的完整轨迹为:
| 批次base | I | 过滤后的局部S | 全局出现 | 输出v | 本批和 |
|---|---|---|---|---|---|
| 0 | [0,1,2,3] | [0,1] | [0,1] | [10,10] | 20 |
| 4 | [4,5,6,7] | [1,3] | [5,7] | [50,70] | 120 |
| 8 | [8,9,10,11] | [2] | [10] | [100] | 100 |
合并为出现 [0,1,5,7,10],输出 [10,10,50,70,100],SUM=240。位置0和1的值都为10,仍贡献两次。位置6的 y validity 为0,比较 y=1 得 UNKNOWN;本查询不会保留它。
K=1、2、4、5、12 都得到相同五份输出。K=5 的末批 base=10、n=2,只有局部0、1合法,S=[0];旧尾槽2..4无论写着什么都不得进入筛选或投影。K=4 恰好整除12,没有短尾,不能只测试它便声称尾部正确。
不完整压缩与空批陷阱
第二批若只把 x 压紧到局部[1,3],而 y、v 保留原布局,随后按新局部0、1读 v 就会拿到40、50,丢掉70。合法方案是所有相关列和 I 用同一置换压紧,并把 S 重置为0..|S|−1;或者保持全部列不动,只传 S。两种方式可以混合在不同算子,但交接处必须改变映射合同。
K=1 时先返回位置0、1的两个结果,位置2产生 DATA(empty),后面仍有位置5、7、10。若把首次空批当 EOF,就只得到20。空表则第一次拉取即 END,不需要虚构一份全NULL记录。
借用、陷阱与错误不是数值
父算子保存第一批引用,调用 next_batch 取得第二批后再读取旧引用,可能把第一批的10读成第二批的40。借用代次检查应报 StaleBatch;若要晚些使用,须在拉取前复制。close 同样令现存借用失效。数据读失败返回 ERROR,并释放或保留可关闭的资源,不得偷偷返回 END。
活跃掩码并不自动屏蔽硬件异常。迁移例取 d=[1,0],先按 d≠0 选出 S=[0],再投影10/d:标量活跃循环只算10/1;先算整向量[10/1,10/0]再blend会发生除零。尾槽含越界地址时同理。要扩展第一版的总定义前提,必须明确 inactive lane 不会求值、不会读取非法地址,或用安全替代操作数并证明没有副作用,同时规定活动槽错误的传播规则。SQL表达式的一般重排不承诺短路,这个例子只比较已明确分层的Filter→Project物理合同。
推论与应用
分开调度、计算和搬运
对 N>0 份输入、满批容量 K 的一次扫描,标量数据交付 N 次,批数据交付 ceil(N/K) 次;两者还各有一次终止调用。这是满批扫描的计数;一般“最多K条”的算子可能交付更多小批或空批,必须按实际调用计数。算子内部仍有 N 份谓词工作,除非已证明更强的跳过规则。连续双筛可让第二谓词只处理第一步保留的活动槽,这来自筛选,不是单凭批宽除以K。
若解释分派单价为 d,谓词总成本为 P,位置/掩码处理为 A,gather及输出复制字节成本为 G,可写条件账本 T_batch≈ceil(N/K)d+P+A+G。这个分解不能推出必然提速:小批增加分派,大批可能增大工作集。每批列数据空间为 O(KW),W 是同时驻留的列宽;选择位置 O(K) 个编号、validity约每列K位、对齐与元数据另计。父子同时借用多批时应求同时存活空间之和,而非各自对同一预算声称足够。
本例两列谓词若总是都求值,仍读24个逻辑谓词字段;只改变K不会减少它们。v延迟取值只需五个逻辑值,但设备页读取仍服从行列布局与页并集,不能把五个值等同五次或五分之一次I/O。共同终点把调度次数、候选检查、逻辑字节和堆页分别记账,检查器不使用Python时间证明数据库速度。
从接口迁移到不同访问路径
位图堆访问可先产生候选出现,再以批次送入完整谓词重检;此时 I 通常不连续,不能再仅存base。批次负责“同一出现怎样穿过算子”,位图负责“哪些出现必须被访问”,两项证明彼此组合,不互相替代。
Limit 提前停止可减少后续批的读取,但当前批可能已经多读若干输入;仍须关闭所有子算子。排序、hash build等阻塞算法不会因接口换成批次就消失。若发生ERROR、取消或内存超限,已累加的SUM只是一份未完成状态;有重试权限的上层应重启完整执行或使用另行证明的恢复协议,不能从一个失效借用接着猜测。
参考资料
- Peter Boncz、Marcin Zukowski、Niels Nes,MonetDB/X100: Hyper-Pipelining Query Execution,CIDR 2005,§4.1.1及§4.2,PDF第7–9页:selection vector在Select、Project、Aggr间传播,以及位置驱动的向量原语。本文的带代次标记的借用接口、终止状态、十二行数据与等价证明是自定教学模型,不是X100公开API
- PostgreSQL 18,Logical Operators,§9.1:SQL三值规则与表达式求值顺序边界;本页复用既有三值页,不另推导真值表