Skip to content

PostgreSQL 数据用 DuckDB 加速查询

本页目的: 原始数据在 PostgreSQL 里,如何用 DuckDB 加速分析型查询 (多表 JOIN + 聚合 + 窗口函数)。四种 DuckDB 扩展工作方式:

  1. pg_duckdb + force_execution — PG 内部嵌入 DuckDB 引擎,零网络开销
  2. rds_duckdb — 阿里云 RDS PostgreSQL 特供版,用同步的方式把表列存储化
  3. postgres_scanner — DuckDB 外部连 PG
  4. 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

构建启动

docker compose -f docker-compose-pgext.yml up -d

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 侧维护一份列存副本,之后分析型查询 自动走列存副本加速。

官方文档:使用 DuckDB 加速查询(阿里云 RDS)

以下实测均在 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 批量注册同步, 花括号内以逗号分隔多个表名:

SELECT rds_duckdb.create_duckdb_tables('{test_table,}');

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 安装

INSTALL postgres_scanner;
LOAD postgres_scanner;

4.2 连接 PG

ATTACH 'dbname=devdb user=dev password=dev host=127.0.0.1 port=5432'
       AS pg (TYPE postgres);

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:

EXPLAIN SELECT count(*) FROM pg.demo.orders WHERE order_date >= DATE '2026-06-01';
┌─────────────┴─────────────┐
│       POSTGRES_SCAN       │
│       Table: orders       │
│          Filters:         │
│ order_date>='2026-06-01': │
│           :DATE           │
│       status='paid'       │
└───────────────────────────┘

4.5 写回 PG

CREATE OR REPLACE TABLE pg.demo.orders AS SELECT * FROM orders;

局限: 大表数据经网络传输,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 导出 列存压缩,对象存储低成本

通用原则

  1. 在线服务 → 继续用 PG(事务、点查、写入)
  2. 快速探索 → 用 pg_duckdb + force_execution 在 PG 会话内测 DuckDB 加速效果
  3. 阿里云 RDS 生产库 → 用 rds_duckdb 同步列存副本,DDL/drop 自动处理,省运维
  4. 生产分析 → 导出 Parquet 后用 DuckDB 查询,或定时增量同步
  5. 数据量更大时 → parquet 可放对象存储(R2/S3)配合 httpfs 扩展

8. 参考链接

资源 链接
阿里云 RDS 官方文档 https://help.aliyun.com/zh/rds/apsaradb-rds-for-postgresql/how-to-use-duckdb-to-speed-up-queries/
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 (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 项目核心原理与代码阅读指南"
Links (2)