PostgreSQL 数据用 DuckDB 加速查询
本页目的: 原始数据在 PostgreSQL 里,如何用 DuckDB 加速分析型查询 (多表 JOIN + 聚合 + 窗口函数)。三种 DuckDB 扩展工作方式:
- pg_duckdb + force_execution — PG 内部嵌入 DuckDB 引擎,零网络开销
- postgres_scanner — DuckDB 外部连 PG
- Parquet 导出 — 格式转换后本地列存查询
附本机实测基准对比。数据源见 模拟数据,实验环境: MacBook(arm64)+ Docker 内 PostgreSQL 18.4,100 万订单 + 10 万客户。
选型对比
| 维度 | pg_duckdb + force_execution | postgres_scanner | Parquet 导出 |
|---|---|---|---|
| 前提 | PG 需安装 pg_duckdb 扩展 | 无(标准 PG 即可) | 需 postgres_scanner 导出一步 |
| 网络开销 | 零(同进程) | 有(PG 线协议) | 零(导出后解耦) |
| 数据新鲜度 | 实时 | 实时 | 取决于同步频率 |
| 复杂度 | 低(一行 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["② postgres_scanner"]
B1["DuckDB (Python/CLI)
postgres_scanner"] -->|PG wire protocol| B2[PostgreSQL]
B2 -->|rows over network| B1
end
subgraph M3["③ 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. 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[加速查询]
3.1 安装
3.2 连接 PG
3.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;
3.4 谓词下推验证
WHERE 条件会推到 PG 执行,只把过滤后的行拉回 DuckDB:
┌─────────────┴─────────────┐
│ POSTGRES_SCAN │
│ Table: orders │
│ Filters: │
│ order_date>='2026-06-01': │
│ :DATE │
│ status='paid' │
└───────────────────────────┘
3.5 写回 PG
局限: 大表数据经网络传输,JOIN 在 DuckDB 侧做,提速有限(约 1.8x)。 适合临时/adhoc 查询,不适合高频分析。
4. Parquet 导出
把 PG 表导出为 Parquet 列存格式,之后 DuckDB 本地查询,与 PG 完全解耦。
flowchart LR
PG[PostgreSQL] -->|COPY TO Parquet| PF[(Parquet File)]
DD[DuckDB] -->|本地读取 列存| PF
DD -->|向量化执行| Result[56x 加速]
4.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 文件。
4.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 在线负载。
5. 基准对比(本机实测)
同一分析查询(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 路径优势越明显。
6. 实践建议
什么时候用哪个
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 临时 adhoc 分析、调试 | force_execution | 一行 SET 开箱,零配置 |
| 对比验证 DuckDB 加速效果 | force_execution | 同会话内 switch 对比,结果最直接 |
| PG 没装任何扩展、只能从外部连 | postgres_scanner | 唯一选项,PG 端无侵入 |
| 生产报表、定时看板 | Parquet 导出 | 56x 加速,与 PG 解耦,不影响在线负载 |
| 数据归档、长周期分析 | Parquet 导出 | 列存压缩,对象存储低成本 |
通用原则
- 在线服务 → 继续用 PG(事务、点查、写入)
- 快速探索 → 用 pg_duckdb + force_execution 在 PG 会话内测 DuckDB 加速效果
- 生产分析 → 导出 Parquet 后用 DuckDB 查询,或定时增量同步
- 数据量更大时 → parquet 可放对象存储(R2/S3)配合
httpfs扩展
7. 参考链接
| 资源 | 链接 |
|---|---|
| DuckDB Postgres 扩展 | https://duckdb.org/docs/stable/core_extensions/postgres |
| Parquet 扩展 | https://duckdb.org/docs/stable/core_extensions/parquet |
| pg_duckdb | https://github.com/duckdb/pg_duckdb |
| pglayers | https://github.com/pglayers/pglayers |
| httpfs | https://duckdb.org/docs/stable/core_extensions/httpfs |
→ 返回目录:DuckDB 实战研究
Backlinks (2)
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["Plans"]
n12["对象存储基本用法(Bucket / Object / 常用操作)"]
n13["挂载 Bucket 为本地文件系统(FUSE Mount)"]
n14["对象存储签名 URL(Signed URL)原理与实战"]
n15["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
n16["Prototypes"]
n17["Research"]
n18["Better Auth 源码阅读指南"]
n19["DuckDB 环境与基本使用"]
n20["DuckDB 实战研究"]
n21["DuckDB 模拟数据"]
n22["PostgreSQL 数据用 DuckDB 加速查询"]
n23["HK 角色阅读"]
n24["HK 台词阅读"]
n25["Hollow Knight 英语主题"]
n26["HK 物品阅读"]
n27["HK 地点阅读"]
n28["HK 世界观 / Lore 阅读"]
n29["HK 英语学习素材清单"]
n30["英语学习 Dashboard"]
n31["English Scraps Archive"]
n32["English Scraps 使用指南"]
n33["Jellyfin 源码阅读指南"]
n34["Lux 资料整理"]
n35["Nest Commander 学习资料"]
n36["NestJS 源码阅读指南"]
n37["Protomaps 自建底图研究"]
n38["自制 PMTiles 地图(最简单例子)"]
n39["MapLibre 集成 Protomaps"]
n40["PMTiles 格式与工具链"]
n41["上海地区底图项目"]
n42["Redash 源码阅读指南"]
n43["Rust 学习计划"]
n44["shadcn/ui 源码阅读指南"]
n45["TRIP 项目核心原理与代码阅读指南"]
n1 --> n20
n6 --> n0
n6 --> n1
n6 --> n2
n6 --> n3
n6 --> n4
n6 --> n5
n6 --> n7
n6 --> n8
n6 --> n9
n6 --> n10
n6 --> n11
n6 --> n17
n12 --> n14
n12 --> n15
n12 --> n16
n13 --> n12
n13 --> n14
n13 --> n15
n14 --> n16
n15 --> n12
n15 --> n14
n15 --> n16
n16 --> n12
n16 --> n13
n16 --> n14
n16 --> n15
n16 --> n37
n17 --> n18
n17 --> n20
n17 --> n30
n17 --> n33
n17 --> n34
n17 --> n35
n17 --> n36
n17 --> n37
n17 --> n42
n17 --> n43
n17 --> n44
n17 --> n45
n19 --> n21
n20 --> n1
n20 --> n17
n20 --> n19
n20 --> n21
n20 --> n22
n21 --> n19
n21 --> n22
n22 --> n20
n22 --> n21
n25 --> n23
n25 --> n24
n25 --> n26
n25 --> n27
n25 --> n28
n25 --> n29
n25 --> n32
n29 --> n24
n30 --> n25
n30 --> n31
n30 --> n32
n32 --> n30
n32 --> n31
n37 --> n8
n37 --> n38
n37 --> n39
n37 --> n40
n37 --> n41
n38 --> n40
n39 --> n16
n39 --> n41
n40 --> n38
n40 --> n41
n41 --> n39
n41 --> n40
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/scraps/plans/" "Plans"
click n12 "../../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
click n13 "../../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
click n14 "../../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
click n15 "../../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
click n16 "../../../../prototypes/" "Prototypes"
click n17 "../../../" "Research"
click n18 "../../better-auth/" "Better Auth 源码阅读指南"
click n19 "../basic-usage/" "DuckDB 环境与基本使用"
click n20 "../" "DuckDB 实战研究"
click n21 "../mock-data/" "DuckDB 模拟数据"
click n22 "./" "PostgreSQL 数据用 DuckDB 加速查询"
click n23 "../../english/hollow-knight/characters/" "HK 角色阅读"
click n24 "../../english/hollow-knight/dialogues/" "HK 台词阅读"
click n25 "../../english/hollow-knight/" "Hollow Knight 英语主题"
click n26 "../../english/hollow-knight/items/" "HK 物品阅读"
click n27 "../../english/hollow-knight/locations/" "HK 地点阅读"
click n28 "../../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
click n29 "../../english/hollow-knight/resources/" "HK 英语学习素材清单"
click n30 "../../english/" "英语学习 Dashboard"
click n31 "../../english/scraps/archive/" "English Scraps Archive"
click n32 "../../english/scraps/" "English Scraps 使用指南"
click n33 "../../jellyfin/" "Jellyfin 源码阅读指南"
click n34 "../../lux/" "Lux 资料整理"
click n35 "../../nest-commander/" "Nest Commander 学习资料"
click n36 "../../nestjs/" "NestJS 源码阅读指南"
click n37 "../../protomaps/" "Protomaps 自建底图研究"
click n38 "../../protomaps/make-own-map/" "自制 PMTiles 地图(最简单例子)"
click n39 "../../protomaps/maplibre/" "MapLibre 集成 Protomaps"
click n40 "../../protomaps/pmtiles/" "PMTiles 格式与工具链"
click n41 "../../protomaps/shanghai-map/" "上海地区底图项目"
click n42 "../../redash/" "Redash 源码阅读指南"
click n43 "../../rust/" "Rust 学习计划"
click n44 "../../shadcn-ui/" "shadcn/ui 源码阅读指南"
click n45 "../../trip/" "TRIP 项目核心原理与代码阅读指南"