对于任何需要维护超大表(更新旧数据、分批删除、数据迁移)的 DBA 或开发者来说,使用
ctid
(元组物理位置)将大表切分为多个小块进行处理是标准操作。然而,直到现在,这种操作都有一个巨大的痛点:
它严格依赖单进程。
随着最近的一个 Commit (
0ca3b169
) 合并入 PostgreSQL 19 (master 分支),
TID 范围扫描(TID Range Scans)终于支持并行了。
该功能由Ivorysql的 Cary Huang 提出并主导开发,由微软的 David Rowley 协助测试及审阅,并最终提交。他们密切合作以完善并行安全逻辑,确保工作进程正确处理扫描限制——最终促成了这个落地到 master 分支的健壮实现。
瀚高以“用开源链接世界”为使命,强调开源技术在数据库基础软件领域的核心作用,致力于通过共享和合作,推动行业发展,同时链接和赋能全球用户。开源技术是中国软件技术发展的必由之路,瀚高作为亚太地区 PostgreSQL 国际社区顶级贡献者之一,长期深度参与 PostgreSQL 国际社区发展与建设。自 2025 年 7 月以来,瀚高被 PostgreSQL 社区采纳的贡献就已超过 2000 行代码。
根据基准测试,新特性的速度提升高达 3 倍。
Postgres 自版本 14 起就支持了
TID Range Scans
。这允许你基于物理块号扫描表的特定切片:
SELECT * FROM my_large_table WHERE ctid >= '(0,0)' AND ctid < '(10000,0)';
这是像 AWS DMS 这样的工具或逻辑复制初始化器拆分海量表的标准方式。问题在于,直到现在这种扫描节点严格来说都是 单工作进程(single worker) 的。
这迫使 Postgres 查询规划器陷入了两难境地。当你在大数据集上运行查询时,规划器必须在以下两者之间做出选择:
TID Range Scan:I/O 高效(只读取你请求的块),但是单工作进程。
Parallel Seq Scan(并行顺序扫描):CPU 高效(占用所有 CPU 内核),但 I/O 浪费(可能会为了过滤而读取超出你范围的块)。
规划器经常会错误地选择并行顺序扫描,CPU 收益似乎超过了 I/O 损耗带来的负面影响。这导致数据库为了利用可用的工作进程,读取了比必要多得多的数据。
Tid Range Scan
参与并行查询计划的基础架构。该逻辑有效地将块范围分配给可用的并行工作进程。不再是一个进程从块 0 扫描到 N,多个工作进程可以并发地获取数据块。
bench_tid_range
,并使用 ctid 范围条件对表的前 50% 运行了
count(*)
查询。
SELECT count(*) FROM bench_tid_range WHERE ctid >= '(0,0)' AND ctid < '(41667,0)'|
|
|
|
|
|---|---|---|---|
| Before (Pg 18) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
| After (Pg 19) |
|
|
|
enable_tidscan = off
并使用 4 个工作进程测试了这一点:如果你维护在 Postgres 实例之间移动数据的内部脚本,你可能编写了手动计算块范围并将巨大的表划分为块、然后生成进程来运行它们的代码。
随着 PostgreSQL 19 的推出,这种复杂性可能可以被删除了。你可以发出更广泛的 TID 范围查询,并相信规划器会有效地在集群的 I/O 和 CPU 资源之间分配工作。
-- 1. 创建表
DROPTABLEIFEXISTS bench_tid_range;
CREATETABLE bench_tid_range (
id
int, payload
text);
-- 2. 插入 10M 行以生成 ~41k 个页面
INSERTINTO bench_tid_range
SELECT x,
'payload_' || x
FROM generate_series(
1,
10000000) x;
-- 3. Vacuum 以设置可见性映射并冻结(对于稳定的基准测试很重要)
VACUUM (
ANALYZE, FREEZE) bench_tid_range;
-- 4. 为会话启用并行
SET max_parallel_workers_per_gather =
4;
-- 尝试 2, 4, 8
SET min_parallel_table_scan_size =
0;
-- 即使对于较小的表也强制并行扫描
-- 5. 运行查询
EXPLAIN (
ANALYZE, BUFFERS)
SELECTcount(*)
FROM bench_tid_range
WHERE ctid >=
'(0,0)'
AND ctid <
'(41667,0)';
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70051178/viewspace-3188018/ ,如需转载,请注明出处,否则将追究法律责任。