内容提要
本文以Postgres两百万行订单表为例,剖析TABLESAMPLE采样机制。SYSTEM按页采样,速度快但样本呈时间片段,易扭曲时间列统计;BERNOULLI按行采样,更均匀但需读全表。REPEATABLE固定种子,但更新、VACUUM FULL、CLUSTER会改变行地址,导致样本漂移。哈希主键或外键可保持子集稳定并维持连接完整性,代价是全表扫描。采样先于WHERE执行,无法用于过滤子集。
延伸解读
采样机制的核心:地址而非行
TABLESAMPLE 的两种内置方法都基于行的物理地址(ctid)进行哈希采样。SYSTEM 对页号哈希,整页保留或丢弃;BERNOULLI 对页号和行指针哈希,逐行决定。这解释了为何 SYSTEM 快但样本呈时间片段,而 BERNOULLI 均匀却需读全表。理解这一点是选择采样方法的关键。
REPEATABLE 的稳定性陷阱
REPEATABLE 固定种子,但只保证地址不变,不保证行不变。UPDATE 会改变行指针,VACUUM FULL 和 CLUSTER 会重写所有行地址,导致样本漂移。若需跨天稳定的子集,应哈希主键或外键,而非依赖地址采样。代价是全表扫描,但可通过表达式索引优化。
连接查询中的采样局限
TABLESAMPLE 只作用于单个表,无法直接采样连接结果。采样订单表后连接客户表,客户表仍需全扫,查询加速有限。若两侧都采样,连接结果会急剧减少,因为匹配对同时被采样的概率极低。保持连接完整性需哈希外键,但同样需全表扫描。
采样与过滤的执行顺序
采样在 WHERE 之前执行,因此无法用于获取过滤后的子集。例如,想采样已发货订单,采样会先于状态过滤,导致样本中可能包含大量非目标行。若需特定子集,应使用哈希键或先过滤再采样,但后者可能失去采样性能优势。
Q&A
PostgreSQL 的 TABLESAMPLE SYSTEM 和 BERNOULLI 有什么区别?
SYSTEM 按页采样,对页号进行哈希,速度快但样本呈时间片段,容易扭曲时间列统计;BERNOULLI 按行采样,对页号和行指针进行哈希,更均匀但需读取全表。
TABLESAMPLE 的 REPEATABLE 能保证样本不变吗?
不能。REPEATABLE 固定种子,但更新、VACUUM FULL、CLUSTER 会改变行地址,导致样本漂移。只有哈希主键或外键才能保持子集稳定。
为什么 TABLESAMPLE SYSTEM 采样时间列会不准确?
因为 SYSTEM 按页采样,而页内行通常按插入顺序排列,导致样本是连续的时间片段,而非随机散布,因此 min、avg 等统计量偏差大。
如何用哈希主键实现稳定的采样?
使用哈希函数如 hashint8extended(id, 42) 对主键哈希,并保留部分桶(如 (hash & 1023) < 10),这样即使表更新、VACUUM FULL 或 CLUSTER,样本行仍保持不变。
TABLESAMPLE 能用于过滤子集吗?
不能。采样先于 WHERE 执行,WHERE 只能对已抽取的样本进行过滤,无法用于获取特定子集。
在连接查询中使用 TABLESAMPLE 有什么问题?
TABLESAMPLE 只缩小其所在的表,连接的另一表仍需全量读取,导致加速有限;若对两表分别采样,连接结果会因独立采样而大幅减少,可能丢失连接完整性。