Skip to content

PostgreSQL 数据用 DuckDB 加速查询

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

  1. pg_duckdb + force_execution — PG 内部嵌入 DuckDB 引擎,零网络开销
  2. postgres_scanner — DuckDB 外部连 PG
  3. 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

构建启动

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

INSTALL postgres_scanner;
LOAD postgres_scanner;

3.2 连接 PG

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

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:

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'       │
└───────────────────────────┘

3.5 写回 PG

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

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

通用原则

  1. 在线服务 → 继续用 PG(事务、点查、写入)
  2. 快速探索 → 用 pg_duckdb + force_execution 在 PG 会话内测 DuckDB 加速效果
  3. 生产分析 → 导出 Parquet 后用 DuckDB 查询,或定时增量同步
  4. 数据量更大时 → 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 项目核心原理与代码阅读指南"
Links (2)