PostgreSQL 数据用 DuckDB 加速查询
本页目的: 原始数据在 PostgreSQL 里,如何用 DuckDB 加速分析型查询 (多表 JOIN + 聚合 + 窗口函数)。四种 DuckDB 扩展工作方式:
- pg_duckdb + force_execution — PG 内部嵌入 DuckDB 引擎,零网络开销
- rds_duckdb — 阿里云 RDS PostgreSQL 特供版,用同步的方式把表列存储化
- postgres_scanner — DuckDB 外部连 PG
- Parquet 导出 — 格式转换后本地列存查询
附本机实测基准对比。数据源见 模拟数据,实验环境: MacBook(arm64)+ Docker 内 PostgreSQL 18.4,100 万订单 + 10 万客户。
选型对比
| 维度 | pg_duckdb + force_execution | rds_duckdb(Ali RDS 特供) | postgres_scanner | Parquet 导出 |
|---|---|---|---|---|
| 前提 | PG 需安装 pg_duckdb 扩展 | 仅阿里云 RDS PostgreSQL | 无(标准 PG 即可) | 需 postgres_scanner 导出一步 |
| 网络开销 | 零(同进程) | 零(同进程 WAL 同步) | 有(PG 线协议) | 零(导出后解耦) |
| 数据新鲜度 | 实时 | WAL 增量同步,接近实时 | 实时 | 取决于同步频率 |
| 复杂度 | 低(一行 SET) | 低(一行注册同步表) | 低 | 中(需导出流程/定时任务) |
| 适合场景 | 临时分析、对比验证、调试 | 阿里云生产库分析加速 | PG 受限环境、无法装扩展 | 生产报表、定时看板、归档 |
flowchart TD
subgraph M1["① pg_duckdb + force_execution"]
A1[psql] -->|SET duckdb.force_execution=true| A2[PG with pg_duckdb]
A2 -->|DuckDB engine in-process| A3[零网络开销]
end
subgraph M2["② rds_duckdb"]
D1[RDS PostgreSQL] -->|WAL 同步 列存储化| D2[(DuckDB 列存副本)]
D1 -->|"SELECT 转发列存副本(开关见 §3.4)"| D2
end
subgraph M3["③ postgres_scanner"]
B1["DuckDB (Python/CLI)
postgres_scanner"] -->|PG wire protocol| B2[PostgreSQL]
B2 -->|rows over network| B1
end
subgraph M4["④ Parquet 导出"]
C1[PostgreSQL] -->|COPY TO| C2[(Parquet File)]
C3[DuckDB] -->|本地读取 列存| C2
end
1. 思路:PG 扛 OLTP,DuckDB 扛 OLAP
PostgreSQL 是 OLTP 数据库(行存、事务、点查快);分析型查询要扫全表 + 大 JOIN + 聚合,行存 + 网络往返都很吃亏。DuckDB 是进程内 OLAP 引擎 (列存、向量化、零网络开销),适合分析负载。
常见组合:PG 继续提供在线服务,分析/报表查询交给 DuckDB。
2. pg_duckdb + force_execution(PG 内嵌 DuckDB 引擎)
在 PG 内部加载 pg_duckdb 扩展,嵌入 DuckDB 执行引擎, 通过 duckdb.force_execution 切换当前会话的查询引擎。
flowchart LR
subgraph Docker["Docker Container"]
PG["PostgreSQL
pg_duckdb loaded"]
end
Client[psql] -->|SET duckdb.force_execution=true| PG
PG -->|DuckDB engine| Result[加速查询]
2.1 前提:构建含 pg_duckdb 的 PG 镜像
用 pglayers 的预编译扩展层 (ghcr.io/pglayers/pgx-*)通过 multi-stage COPY 构建,无需源码编译。
Dockerfile 核心(完整文件见 yaso/packages/database/Dockerfile.pgext):
FROM postgres:18
# 自由组合所需扩展,每加一个就多一行 COPY
COPY --from=ghcr.io/pglayers/pgx-pg_duckdb:18 / /extensions/pg_duckdb/
COPY --from=ghcr.io/pglayers/pgx-pgvector:18 / /extensions/pgvector/
# 动态追加扩展路径到 PG 配置
RUN for ext in pg_duckdb pgvector; do \
echo "extension_control_path = '/extensions/$ext/share:\$system'" \
>> /usr/share/postgresql/postgresql.conf.sample; \
echo "dynamic_library_path = '/extensions/$ext/lib:\$libdir'" \
>> /usr/share/postgresql/postgresql.conf.sample; \
done
docker-compose.yml 核心(完整文件见 yaso/packages/database/docker-compose-pgext.yml):
services:
postgres-dev:
build:
context: .
dockerfile: Dockerfile.pgext
ports:
- "5432:5432"
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: devdb
command:
- bash
- -c
- |
exec docker-entrypoint.sh postgres \
-c shared_preload_libraries=pg_duckdb,vector
构建启动:
2.2 创建扩展并验证
CREATE EXTENSION IF NOT EXISTS pg_duckdb;
-- 验证扩展已注册
SELECT * FROM pg_extension WHERE extname = 'pg_duckdb';
-- 验证 DuckDB 引擎可用
SELECT duckdb.query('SELECT version()');
2.3 测试 force_execution
-- 查看当前模式(默认 false)
SHOW duckdb.force_execution;
-- 切换到 DuckDB 引擎执行
SET duckdb.force_execution = true;
SELECT count(*) FROM orders WHERE order_date >= DATE '2026-06-01';
-- 切回 PG 原生引擎对比
SET duckdb.force_execution = false;
SELECT count(*) FROM orders WHERE order_date >= DATE '2026-06-01';
duckdb.force_execution 按会话设置,不持久化。分析会话中临时开启, OLTP 写入保持 false。
注意: 数据在 PG 本地,零网络开销,但受 PG 行存格式限制, 提速效果不如 Parquet 列存。
3. 阿里云 RDS 专用:rds_duckdb(WAL 同步列存副本)
阿里云 RDS PostgreSQL 内置的 DuckDB 分析加速插件 rds_duckdb, 与社区 pg_duckdb 定位类似。核心区别: 不是把查询临时转发给内嵌 DuckDB 引擎,而是先用「同步」的方式 把表列存储化 —— 在 DuckDB 侧维护一份列存副本,之后分析型查询 自动走列存副本加速。
以下实测均在 rds_duckdb 1.5.1.2(阿里云 RDS PostgreSQL)上完成。
3.1 rds_duckdb 与 pg_duckdb 的区别
| 维度 | pg_duckdb | rds_duckdb |
|---|---|---|
| 核心机制 | 查询路由到内嵌引擎实时执行 | 先同步生成 DuckDB 列存副本,再走副本 |
| 数据形态 | 仍读 PG 行存 | DuckDB 列存(本地副本) |
| 同步机制 | 无,实时执行 | 基于 PG WAL / LSN 增量同步 |
| 结构变更 | 实时生效 | DDL 变更、drop table 按 LSN 自动处理 |
| 适用环境 | 自建 PG / 任意 PG | 仅阿里云 RDS PostgreSQL |
3.2 创建扩展并检查
CREATE EXTENSION IF NOT EXISTS rds_duckdb;
-- 检查扩展是否注册
SELECT * FROM pg_extension WHERE extname = 'rds_duckdb';
psql 交互模式也可以直接 \dx rds_duckdb 查看。
3.3 注册同步表(把表列存储化)
先建好要加速的 PG 原表(如 test_table),再用 rds_duckdb.create_duckdb_tables 批量注册同步, 花括号内以逗号分隔多个表名:
DDL / drop 自动处理: 注册后,PG 侧对表的 DDL 变更、 甚至 drop table,都会根据 PG WAL 的 LSN 自动同步处理, 无需手工重建 DuckDB 副本。
3.4 查看同步状态与延迟
SELECT sync_table,
sync_status_description,
confirmed_lsn,
(SELECT pg_current_wal_lsn()) AS current_lsn,
pg_wal_lsn_diff((SELECT pg_current_wal_lsn()), confirmed_lsn) AS lag_bytes
FROM rds_duckdb.duckdb_sync_stat;
confirmed_lsn:DuckDB 副本已应用到的 WAL 位置current_lsn:当前 PG 的 WAL 位置lag_bytes:两者 WAL 距离,即增量同步落后的字节数sync_status_description:同步状态(syncing / not syncing 等)
官方文档还支持
SET rds_duckdb.execution = on(或语句级 Hint) 让 SELECT 显式走 DuckDB 执行,与 pg_duckdb 的 force_execution 类似; 本页未实测该开关,细节以官方文档为准。
4. postgres_scanner(DuckDB 外部连接 PG)
DuckDB 侧的扩展,从外部进程(Python/CLI)连接 PG 读取数据, PG 端无需安装任何额外扩展,标准 PG 实例即可。
flowchart LR
subgraph Process["DuckDB 进程 (Python/CLI)"]
DD["DuckDB
postgres_scanner"]
end
subgraph Server["PG 服务器 (Docker/Remote)"]
PG[PostgreSQL
标准实例即可]
end
DD -->|PG wire protocol| PG
PG -->|rows over network| DD
DD -->|向量化执行| Result[加速查询]
4.1 安装
4.2 连接 PG
4.3 查询
-- 像本地表一样查询
SELECT count(*) FROM pg.demo.orders WHERE order_date >= DATE '2026-06-01';
-- 多表 JOIN
SELECT city, count(*) AS n, round(sum(o.amount), 0) AS amt
FROM pg.demo.orders o
JOIN pg.demo.customers c ON o.customer_id = c.id
WHERE o.order_date >= DATE '2025-08-01'
GROUP BY city ORDER BY amt DESC LIMIT 5;
4.4 谓词下推验证
WHERE 条件会推到 PG 执行,只把过滤后的行拉回 DuckDB:
┌─────────────┴─────────────┐
│ POSTGRES_SCAN │
│ Table: orders │
│ Filters: │
│ order_date>='2026-06-01': │
│ :DATE │
│ status='paid' │
└───────────────────────────┘
4.5 写回 PG
局限: 大表数据经网络传输,JOIN 在 DuckDB 侧做,提速有限(约 1.8x)。 适合临时/adhoc 查询,不适合高频分析。
5. Parquet 导出
把 PG 表导出为 Parquet 列存格式,之后 DuckDB 本地查询,与 PG 完全解耦。
flowchart LR
PG[PostgreSQL] -->|COPY TO Parquet| PF[(Parquet File)]
DD[DuckDB] -->|本地读取 列存| PF
DD -->|向量化执行| Result[56x 加速]
5.1 导出
-- 在 DuckDB 中执行
LOAD postgres_scanner;
ATTACH 'dbname=devdb user=dev password=dev host=127.0.0.1 port=5432'
AS pg (TYPE postgres);
COPY pg.demo.orders TO 'parquet/orders.parquet' (FORMAT PARQUET);
本机实测:100 万行导出耗时约 0.5 秒,产出 14 MB 的 parquet 文件。
5.2 本地查询
-- 与 PG 完全解耦,零网络开销
SELECT city, count(*) AS n, round(sum(o.amount), 0) AS amt
FROM 'parquet/orders.parquet' o
JOIN 'parquet/customers.parquet' c ON o.customer_id = c.id
WHERE o.order_date >= DATE '2025-08-01'
GROUP BY city ORDER BY amt DESC LIMIT 5;
同步链路(可选): 定时任务增量导出(
WHERE updated_at > last_sync), DuckDB 侧只读 parquet,长期为报表提供高速查询,不影响 PG 在线负载。
6. 基准对比(本机实测)
同一分析查询(orders JOIN customers,近一年按月/城市聚合 + 排序):
| 执行方式 | 耗时 | 相对 PG |
|---|---|---|
| PostgreSQL 直接执行 | 405ms | 1.0x |
| pg_duckdb force_execution | 80.9ms | ~5.0x |
| DuckDB 直连 PG(postgres_scanner) | 112.4ms | ~3.6x |
| DuckDB 查本地 Parquet | 7.2ms | ~56x |
结论:
- force_execution 快 5 倍 — DuckDB 引擎在 PG 进程内执行,零网络开销,不受 PG 行存限制
- postgres_scanner 快 3.6 倍 — 瓶颈是网络传输 + PG 侧扫描
- Parquet 快 56 倍 — 列存 + 向量化 + 无网络开销全面生效
- 结果一致:PG 与 DuckDB(Parquet)两端核对相同(78 个分组、总额 259,418,325.99)
PG 侧没建索引,计划是并行 Seq Scan + 外部排序;对分析查询而言索引帮不上大忙。 数据量越大、查询越重,Parquet 路径优势越明显。
7. 实践建议
什么时候用哪个
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 临时 adhoc 分析、调试 | force_execution | 一行 SET 开箱,零配置 |
| 对比验证 DuckDB 加速效果 | force_execution | 同会话内 switch 对比,结果最直接 |
| 阿里云 RDS 生产库分析加速 | rds_duckdb | WAL 同步列存副本,DDL/drop 自动处理 |
| PG 没装任何扩展、只能从外部连 | postgres_scanner | 唯一选项,PG 端无侵入 |
| 生产报表、定时看板 | Parquet 导出 | 56x 加速,与 PG 解耦,不影响在线负载 |
| 数据归档、长周期分析 | Parquet 导出 | 列存压缩,对象存储低成本 |
通用原则
- 在线服务 → 继续用 PG(事务、点查、写入)
- 快速探索 → 用 pg_duckdb + force_execution 在 PG 会话内测 DuckDB 加速效果
- 阿里云 RDS 生产库 → 用 rds_duckdb 同步列存副本,DDL/drop 自动处理,省运维
- 生产分析 → 导出 Parquet 后用 DuckDB 查询,或定时增量同步
- 数据量更大时 → parquet 可放对象存储(R2/S3)配合
httpfs扩展
8. 参考链接
→ 返回目录:DuckDB 实战研究
Backlinks (3)
flowchart LR
n0["AI"]
n1["Database"]
n2["Dev Tools"]
n3["Emoji"]
n4["Frontend"]
n5["Game Dev"]
n6["Collection"]
n7["Languages"]
n8["Maps"]
n9["Media"]
n10["Monitor"]
n11["Study Materials"]
n12["Plans"]
n13["Storage"]
n14["对象存储基本用法(Bucket / Object / 常用操作)"]
n15["挂载 Bucket 为本地文件系统(FUSE Mount)"]
n16["对象存储签名 URL(Signed URL)原理与实战"]
n17["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
n18["Prototypes"]
n19["Research"]
n20["Better Auth 源码阅读指南"]
n21["DuckDB 环境与基本使用"]
n22["DuckDB 实战研究"]
n23["DuckDB 模拟数据"]
n24["PostgreSQL 数据用 DuckDB 加速查询"]
n25["HK 角色阅读"]
n26["HK 台词阅读"]
n27["Hollow Knight 英语主题"]
n28["HK 物品阅读"]
n29["HK 地点阅读"]
n30["HK 世界观 / Lore 阅读"]
n31["英语学习 Dashboard"]
n32["English Scraps Archive"]
n33["English Scraps 使用指南"]
n34["Jellyfin 源码阅读指南"]
n35["Lux 资料整理"]
n36["Nest Commander 学习资料"]
n37["NestJS 源码阅读指南"]
n38["Protomaps 自建底图研究"]
n39["自制 PMTiles 地图(最简单例子)"]
n40["MapLibre 集成 Protomaps"]
n41["PMTiles 格式与工具链"]
n42["上海地区底图项目"]
n43["Redash 源码阅读指南"]
n44["Rust 学习计划"]
n45["shadcn/ui 进阶使用"]
n46["shadcn/ui 组件添加与基本使用"]
n47["shadcn/ui 实用研究(从环境到基本使用)"]
n48["shadcn/ui 环境与初始化"]
n49["TRIP 项目核心原理与代码阅读指南"]
n1 --> n22
n1 --> n24
n4 --> n47
n6 --> n0
n6 --> n1
n6 --> n2
n6 --> n3
n6 --> n4
n6 --> n5
n6 --> n7
n6 --> n8
n6 --> n9
n6 --> n10
n6 --> n11
n6 --> n12
n6 --> n13
n6 --> n19
n14 --> n16
n14 --> n17
n14 --> n18
n15 --> n14
n15 --> n16
n15 --> n17
n16 --> n18
n17 --> n14
n17 --> n16
n17 --> n18
n18 --> n14
n18 --> n15
n18 --> n16
n18 --> n17
n18 --> n38
n19 --> n20
n19 --> n22
n19 --> n31
n19 --> n34
n19 --> n35
n19 --> n36
n19 --> n37
n19 --> n38
n19 --> n43
n19 --> n44
n19 --> n47
n19 --> n49
n21 --> n23
n22 --> n1
n22 --> n19
n22 --> n21
n22 --> n23
n22 --> n24
n23 --> n21
n23 --> n24
n24 --> n22
n24 --> n23
n27 --> n25
n27 --> n26
n27 --> n28
n27 --> n29
n27 --> n30
n27 --> n33
n31 --> n27
n31 --> n32
n31 --> n33
n33 --> n31
n33 --> n32
n38 --> n8
n38 --> n39
n38 --> n40
n38 --> n41
n38 --> n42
n39 --> n41
n40 --> n18
n40 --> n42
n41 --> n39
n41 --> n42
n42 --> n40
n42 --> n41
n45 --> n46
n46 --> n45
n46 --> n48
n47 --> n4
n47 --> n19
n47 --> n45
n47 --> n46
n47 --> n48
n48 --> n46
click n0 "../../../../collection/ai/" "AI"
click n1 "../../../../collection/database/" "Database"
click n2 "../../../../collection/dev-tools/" "Dev Tools"
click n3 "../../../../collection/emoji/" "Emoji"
click n4 "../../../../collection/frontend/" "Frontend"
click n5 "../../../../collection/game-dev/" "Game Dev"
click n6 "../../../../collection/" "Collection"
click n7 "../../../../collection/languages/" "Languages"
click n8 "../../../../collection/maps/" "Maps"
click n9 "../../../../collection/media/" "Media"
click n10 "../../../../collection/monitor/" "Monitor"
click n11 "../../../../collection/reading/" "Study Materials"
click n12 "../../../../collection/scraps/plans/" "Plans"
click n13 "../../../../collection/storage/" "Storage"
click n14 "../../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
click n15 "../../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
click n16 "../../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
click n17 "../../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
click n18 "../../../../prototypes/" "Prototypes"
click n19 "../../../" "Research"
click n20 "../../better-auth/" "Better Auth 源码阅读指南"
click n21 "../basic-usage/" "DuckDB 环境与基本使用"
click n22 "../" "DuckDB 实战研究"
click n23 "../mock-data/" "DuckDB 模拟数据"
click n24 "./" "PostgreSQL 数据用 DuckDB 加速查询"
click n25 "../../english/hollow-knight/characters/" "HK 角色阅读"
click n26 "../../english/hollow-knight/dialogues/" "HK 台词阅读"
click n27 "../../english/hollow-knight/" "Hollow Knight 英语主题"
click n28 "../../english/hollow-knight/items/" "HK 物品阅读"
click n29 "../../english/hollow-knight/locations/" "HK 地点阅读"
click n30 "../../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
click n31 "../../english/" "英语学习 Dashboard"
click n32 "../../english/scraps/archive/" "English Scraps Archive"
click n33 "../../english/scraps/" "English Scraps 使用指南"
click n34 "../../jellyfin/" "Jellyfin 源码阅读指南"
click n35 "../../lux/" "Lux 资料整理"
click n36 "../../nest-commander/" "Nest Commander 学习资料"
click n37 "../../nestjs/" "NestJS 源码阅读指南"
click n38 "../../protomaps/" "Protomaps 自建底图研究"
click n39 "../../protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
click n40 "../../protomaps/maplibre/" "MapLibre 集成 Protomaps"
click n41 "../../protomaps/pmtiles/" "PMTiles 格式与工具链"
click n42 "../../protomaps/shanghai-map/" "上海地区底图项目"
click n43 "../../redash/" "Redash 源码阅读指南"
click n44 "../../rust/" "Rust 学习计划"
click n45 "../../shadcn-ui/advanced/" "shadcn/ui 进阶使用"
click n46 "../../shadcn-ui/components/" "shadcn/ui 组件添加与基本使用"
click n47 "../../shadcn-ui/" "shadcn/ui 实用研究(从环境到基本使用)"
click n48 "../../shadcn-ui/setup/" "shadcn/ui 环境与初始化"
click n49 "../../trip/" "TRIP 项目核心原理与代码阅读指南"