性能与运维

本节目标

读完本篇,你应能:

  • 用 EXPLAIN 查看物理执行计划,用 EXPLAIN ANALYZE 在真实执行中拿到各算子耗时与基数
  • 通过 SET 调整 memory_limit、threads、temp_directory 等官方配置项,理解内存与并行对分析负载的影响
  • 知道 Min-Max(zonemap) 与 ART 索引各自适用场景,避免误建大 ART 索引
  • 掌握扩展来源、不可信 SQL 与嵌入 (embedding) 场景下的基本安全思路
  • 按清单排查 OOM(内存耗尽) 与慢查询常见问题

若尚未熟悉远程读与扩展加载,请先阅读 常用扩展。全系列索引见 DuckDB 教程。

性能调优总览

DuckDB 默认配置已针对分析型负载做了较多优化;多数场景下先写清晰 SQL、选对文件格式(如 Parquet),再按需微调内存与线程即可。官方 Performance Guide 按导入、连接、Join、索引等主题展开;本篇聚焦日常运维与排错入口。

EXPLAIN:查看查询计划

在任意查询前加上 EXPLAIN,会输出即将执行的物理计划 (physical plan)——一棵按顺序执行的算子树。优化器会在合法范围内改写计划以降低成本。

CREATE TABLE students (name VARCHAR, sid INTEGER);
CREATE TABLE exams (eid INTEGER, subject VARCHAR, sid INTEGER);

EXPLAIN
SELECT name
FROM students
JOIN exams USING (sid)
WHERE name LIKE 'Ma%';

阅读计划时可关注:扫描方式(全表 vs 过滤后)、Join 顺序与类型、是否出现 Hash Aggregate 等。计划文本较长时,可配合官方 EXPLAIN 指南 对照算子含义。

说明:EXPLAIN 不执行查询主体(仅生成计划),适合在生产数据上安全查看「将会怎么做」。

EXPLAIN ANALYZE:剖析真实执行

EXPLAIN ANALYZE 会真正跑完查询,并在计划中填入:

  • 各算子的累计墙钟时间
  • 估计基数 EC 与实际基数
EXPLAIN ANALYZE
SELECT name
FROM students
JOIN exams USING (sid)
WHERE name LIKE 'Ma%';

注意:

  1. 多线程并行时,各算子时间之和可能大于查询总墙钟时间(时间在多个线程上重叠统计)。
  2. 读多个 Parquet 等文件时,输出里可能列出具体文件名,便于定位慢文件。
  3. 会在真实数据上产生完整负载,勿对超大生产表随意在高峰时段执行。

慢查询优化典型流程:EXPLAIN ANALYZE → 找耗时占比最高的算子 → 改 SQL / 分区 / 谓词下推 / 调整资源。

内存与线程:SET 配置

DuckDB 的运行参数主要通过 SET 配置名 = '值' 修改(会话级;持久化到数据库文件时可用 SET GLOBAL 等,详见官方 Configuration)。下列为运维中最常碰到的官方配置名:

配置名作用简述
memory_limit引擎可用内存上限(如 '4GB'、'80%')。过低易 spill 或 OOM,过高可能与同机其它进程争抢。
threads查询并行使用的 worker 线程数。CPU 密集时可设为物理核数附近;与外部调度器共存时可适当调低。
temp_directory内存不足时溢出 (spill) 临时文件的目录。磁盘慢会直接拖慢大 Join / 大排序。
max_temp_directory_size临时目录占用上限,防止 spill 写满磁盘。

示例(CLI 或 Python 连接后执行):

SET memory_limit = '8GB';
SET threads = 8;
SET temp_directory = '/tmp/duckdb_spill';
SET max_temp_directory_size = '100GB';

查看当前值:

SELECT name, value FROM duckdb_settings()
WHERE name IN ('memory_limit', 'threads', 'temp_directory', 'max_temp_directory_size');

Python 嵌入时可在连接字符串或连接后 SET,与 CLI 一致;多连接共享同一 :memory: 或文件库时注意后设的会话配置只影响当前连接(除非使用 GLOBAL)。

与性能相关的还有 preserve_insertion_order 等开关:保序有时会增加内存或算子成本,批 ETL 若不要求行序可查阅官方说明后再关闭。

关于 PRAGMA:DuckDB 以 SET / RESET 为主管理配置;个别历史或兼容场景可能出现 PRAGMA,以 Configuration 列出的名称为准,不要照搬其它数据库的 PRAGMA 习惯。

索引:何时值得建

DuckDB 内置两类与索引密切相关的结构(详见 Indexes):

Min-Max Index(Zonemap)

对通用标量列自动维护块级最小/最大值,用于跳过明显不相关的数据块。无需手动 CREATE INDEX;保持统计信息有效、谓词可下推即可受益。

Adaptive Radix Tree(ART)

主要用于 PRIMARY KEY / UNIQUE 约束,以及点查、极高选择性(约 < 0.1% 行)过滤。可手动:

CREATE INDEX idx_orders_id ON orders(order_id);

慎用场景:

  • 低选择性列上的 ART(维护成本高、收益小)
  • 索引构建阶段 ART 须能放进内存——若整列索引在构建时放不下内存,应避免强上 ART,改从分区、排序键、Parquet 布局等方向优化

扩展还可提供索引类型(如 spatial 的 R-tree),用法见对应扩展文档。

安全与运维边界

DuckDB 能读写文件、访问网络、加载扩展并占用 CPU/内存,与 Bash/Python 类似,以运行进程的用户权限为上限。

不可信 SQL

勿直接执行用户粘贴的任意 SQL。若业务必须接受外部 SQL,需沙箱而非仅依赖单个 SET:

  • duckdb-wasm 等隔离运行时
  • 容器 / 独立进程 / 最小权限 OS 账户
  • 应用层查询超时与资源配额

官方 Securing DuckDB 描述 enable_external_access 等纵深防御选项;它们不能替代沙箱,但可限制文件与网络访问面。

扩展来源

  • 默认从 extensions.duckdb.org 的 core 仓库安装经构建的扩展;INSTALL ... FROM 仓库名 会换源(如 core_nightly)。
  • 只从可信仓库安装;版本须与 DuckDB 主版本匹配(见 常用扩展)。
  • 生产环境避免随意加载未审计的自定义扩展二进制。

嵌入 (Embedding) 应用

在应用内嵌 DuckDB(Python、C++、Rust 等)时,除 SQL 注入与资源耗尽外,还需考虑:

  • 数据库文件与临时目录的文件系统权限
  • 是否允许扩展自动安装、是否禁用外部访问
  • 多租户时进程或实例隔离

详见官方 Embedding DuckDB。

常见排错清单

OOM 或内存相关报错

  1. 查看 / 调低并发:其它进程与 threads 是否叠加占满 RAM。
  2. 检查 memory_limit 是否过小;或相对机器物理内存过大导致 OS 杀进程。
  3. 大 Join、大窗口、大 ORDER BY:用 EXPLAIN ANALYZE 看是否大量 spill;确认 temp_directory 磁盘空间与 max_temp_directory_size。
  4. 是否在建 ART 索引或一次性读入超大宽表;能否改分批、Parquet 列裁剪、COPY 分块。
  5. 嵌入场景:多个连接各设高 memory_limit 导致总和超限。

慢查询

  1. EXPLAIN ANALYZE 定位最慢算子(扫描、Join、Aggregate)。
  2. 过滤是否写在 WHERE 而非外层;Join 键类型是否一致、能否减少中间结果宽度。
  3. 远程路径(S3/HTTP):网络、LIST 次数、是否 LOAD httpfs 与凭证正确(见 常用扩展)。
  4. 文件格式:CSV 全表扫 vs Parquet 谓词下推;小文件过多时考虑合并。
  5. 误建或过多 ART 索引导致写入与维护变慢。
  6. threads = 1 与过高线程两种极端都可能在特定硬件上变慢,可对比测试。

扩展与安全类

  1. 扩展加载失败 → 重装 INSTALL、核对 DuckDB 与扩展版本。
  2. 非预期网络/文件访问 → 查 enable_external_access 与 Securing 文档中的锁定配置。

延伸阅读


上一篇:常用扩展 · 下一篇:Go 客户端 · 系列首页:DuckDB 教程