DuckDB 安装部署、架构边界与本地分析提效手册
当你只想查一个 Parquet,却不想先养一套集群
排查导出数据时,常见需求只是确认 schema、行数、空值和几条聚合,却要先申请数据库、端口和账号。DuckDB 把分析引擎嵌进当前进程:CLI、Python、Java 或 Node 直接打开 :memory:、.duckdb 文件、CSV、Parquet 或 JSON,不需要独立 server,也没有默认监听端口。它因此很适合开发机分析、测试 fixture、CI 数据断言和短生命周期交付包。
这种轻量来自边界收缩,不是把 ClickHouse 或 Doris 缩小。DuckDB 默认继承宿主进程的文件、网络和 CPU 权限,没有服务端账号、租户隔离和高可用接管。出现多个独立用户、长期在线查询、统一审计、持续导入或资源隔离需求时,继续套一层 HTTP 服务只会把权限、并发和恢复责任藏起来;这时应选择共享数据库、数据湖目录或分析平台。
先固定入口、目录和样例
开始实验需要:
一个只用于实验的项目目录,例如 your-project/。至少一种入口:DuckDB CLI、Python、Java JDBC、Node Neo 或 Docker。不要把“只有某个同事本机装了 CLI”当团队能力。一个可清理的数据目录,例如 data/sample/、var/duckdb/、var/duckdb/tmp/。
一份脱敏样例数据,优先使用 CSV / Parquet / JSON 小文件。不要把真实手机号、邮箱、身份证号、订单号、精确地理位置、访问 token 或生产日志明细放进仓库。如果要访问 HTTP / S3 文件,先确认网络、代理、CA 证书、对象存储权限和凭证注入方式。如果要在 CI 中运行,先确认 runner 的磁盘空间、临时目录清理、并行任务隔离和 extension 安装策略。
DuckDB 没有默认服务端口。判断它是否可用,不能去看 docker ps 或端口监听,而要验证 CLI / 语言进程能否执行查询、读取样例文件、写入受控输出并清理本地文件。
先检查入口:
duckdb -version
python -c "import duckdb; print(duckdb.sql('select 1').fetchall())"
java -version
node --version
docker image ls duckdb/duckdb这些命令不是要求全部具备。团队要选择一个主入口并锁定版本,其他客户端只有在项目实际使用时才进入兼容矩阵。安装页 给出 Linux、macOS、Windows 的 current / LTS 入口;下文 CLI、Docker、Python 和 JDBC 使用 1.4.5 LTS 发行线作为可复现示例,Node Neo 使用 1.5.4。不同客户端的包版本格式可能不同,项目锁文件、容器 tag 和 CI 输出中的 SELECT version() 才共同构成兼容基线。
DuckDB 主仓库采用 MIT License。扩展、语言客户端和基础镜像是独立制品,进入企业制品库或对外交付时仍要分别保存它们的版本、来源、校验和与许可证清单,不能把核心许可证自动套到所有扩展上。
| 入口 | 适合 | 不适合 | 必须确认 |
|---|---|---|---|
| CLI | 个人最小验证、数据文件检查、批处理 SQL、交付包验收 | 长期多人共享服务 | 版本、PATH、工作目录、history、文件输出 |
:memory: | 单测、临时 SQL、无需持久化的 fixture | 交付样例和跨进程复用 | 重启后数据消失 |
.duckdb 文件 | 本地缓存、可复现样例库、只读交付 | 多进程写入、长期共享服务 | 文件路径、WAL、锁、只读打开、checkpoint |
| Python | 数据脚本、测试、Notebook、CI 校验 | 多语言团队统一唯一入口 | Python 版本、依赖锁定、连接关闭 |
| Java JDBC | Java 项目最小接入、离线分析任务 | 把 DuckDB 包装成共享服务 | driver 版本、只读属性、连接生命周期 |
| Node Neo | Node 脚本、数据校验、前端工程辅助分析 | 继续沿用已弃用旧客户端 | 官方 Neo 包、项目锁定版本 |
| Docker | 无本机安装权限、CI 容器化验证 | 生产数据库服务 | volume、工作目录、镜像 tag、输出清理 |
| Extensions | HTTP / S3、Excel、Spatial、FTS 等可选能力 | 入门必经流程 | 在线安装、离线包、缓存目录、来源 |
CLI
安装页会按平台给出当前安装方式。下面的 LTS 示例用安装脚本固定 CLI 版本;不允许联网执行安装脚本的环境,应下载对应平台制品、验证页面公布的 SHA256,再放入受控制品库:
curl https://install.duckdb.org | DUCKDB_VERSION=1.4.5 sh
duckdb -version最小验证:
duckdb :memory: "SELECT 1 AS ok;"打开持久文件:
mkdir -p var/duckdb
duckdb var/duckdb/dev.duckdb "CREATE TABLE t AS SELECT 1 AS id;"
duckdb var/duckdb/dev.duckdb "SELECT * FROM t;"只读打开适合交付包验收和多人读取:
duckdb -readonly var/duckdb/dev.duckdb "SELECT count(*) FROM t;"如果只读失败,先检查文件是否存在、路径是否正确、当前进程是否还在写入、权限是否只读、WAL 是否已正常 checkpoint。
Docker
Docker 适合作为“无需安装 CLI 的执行器”,不是 DuckDB server:
docker run --rm \
-v "$PWD:/work" \
-w /work \
duckdb/duckdb:1.4.5 \
duckdb :memory: "SELECT 1 AS ok;"读取项目文件:
docker run --rm \
-v "$PWD:/work" \
-w /work \
duckdb/duckdb:1.4.5 \
duckdb :memory: "SELECT * FROM read_csv('data/sample/events.csv', header=true) LIMIT 5;"官方镜像同时提供 ARM64 与 x86_64 架构。团队模板必须锁定镜像 tag,并记录镜像 digest;升级 current 或 LTS 指针前,先用项目查询、文件格式和扩展用例重跑兼容验证。
Python
安装:
python -m pip install duckdb==1.4.5最小验证:
import duckdb
print(duckdb.sql("SELECT 42 AS answer").fetchall())持久文件:
import os
import duckdb
db_path = os.getenv("DUCKDB_PATH", "var/duckdb/dev.duckdb")
temp_dir = os.getenv("DUCKDB_TEMP_DIR", "var/duckdb/tmp").replace("'", "''")
con = duckdb.connect(db_path)
try:
con.execute(f"SET temp_directory='{temp_dir}'")
con.execute("CREATE TABLE IF NOT EXISTS event_demo AS SELECT 1 AS id, 'demo' AS source")
rows = con.execute("SELECT count(*) FROM event_demo").fetchall()
print(rows)
finally:
con.close()只读读取:
import duckdb
con = duckdb.connect("var/duckdb/dev.duckdb", read_only=True)
try:
print(con.execute("SELECT count(*) FROM event_demo").fetchall())
finally:
con.close()Python 脚本必须显式关闭连接,尤其在 CI 和交付包生成脚本里,否则文件锁、WAL 和 checkpoint 会变成很难解释的偶发问题。
Java JDBC
JDBC 的 Maven 版本带额外修订位,示例将它锁到对应的 LTS 发行:
<dependency>
<groupId>org.duckdb</groupId>
<artifactId>duckdb_jdbc</artifactId>
<version>1.4.5.0</version>
</dependency>最小连接:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class DuckDbVerify {
public static void main(String[] args) throws Exception {
String path = System.getenv().getOrDefault("DUCKDB_PATH", "var/duckdb/dev.duckdb");
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:" + path);
Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT 1 AS ok");
while (rs.next()) {
System.out.println(rs.getInt("ok"));
}
}
}
}只读读取要按官方 JDBC 文档设置 read-only 属性。不要让多个 Java 进程同时写同一个 .duckdb 文件。
Node Neo
官方当前推荐 Node Neo 客户端:
npm install @duckdb/node-api@1.5.4旧 duckdb Node.js package 已进入维护 / 弃用边界,团队新模板使用 Node Neo,并在 package-lock.json 或等价锁文件中固定版本。升级时用项目的查询、类型映射和导出用例验证 API 兼容,不能只看安装成功。
项目目录建议:
dev-dependencies/
duckdb/
README.md
.env.example
sql/
verify.sql
export-package.sql
scripts/
verify-cli.sh
verify-python.py
clean-duckdb.sh
clean-duckdb.ps1
data/
sample-events.csv
sample-events.json
manifest/
sample-package.yml
var/
duckdb/
tmp/
extensions/.env.example:
DUCKDB_PATH=var/duckdb/dev.duckdb
DUCKDB_TEMP_DIR=var/duckdb/tmp
DUCKDB_EXTENSIONS_DIR=var/duckdb/extensions
DUCKDB_ENV=local
DUCKDB_READONLY=false项目 .gitignore 建议覆盖:
*.duckdb
*.duckdb.*
*.wal
*.tmp/
var/duckdb/
!var/duckdb/.gitkeep如果团队确实要交付 .duckdb 文件,不要放普通源码仓库。放到受控制品库,并附 manifest、schema、生成脚本、校验 SQL、checksum、脱敏说明和读取方式。
常用配置:
SET threads = 4;
SET memory_limit = '2GB';
SET temp_directory = 'var/duckdb/tmp';
SET max_temp_directory_size = '10GB';
SET preserve_insertion_order = false;查看配置:
SELECT name, value, description
FROM duckdb_settings()
WHERE name IN ('threads', 'memory_limit', 'temp_directory', 'max_temp_directory_size');配置原则:
本地脚本只给合理上限,不把开发机所有 CPU / 内存吃满。CI 必须设置可清理临时目录,不能默认把 runner 系统盘吃满。大查询输出必须落到 var/duckdb/ 或制品目录,不散落在项目根。
extension cache 和 persistent secrets 要进入团队清理规则。
最小验证必须证明:DuckDB 可执行、能读文件、能建表、能导出、能只读打开、能清理,不只是一条 SELECT 1。
准备样例 CSV:
event_date,event_time,service,event_type,user_id,cost_ms,success
<EVENT_DATE>,<EVENT_TIME>,api,request,1001,42,true
<EVENT_DATE>,<EVENT_TIME>,api,request,1002,180,true
<EVENT_DATE>,<EVENT_TIME>,worker,job,1003,900,false保存为 dev-dependencies/duckdb/data/sample-events.csv。
如果要验证 JSON,再准备 dev-dependencies/duckdb/data/sample-events.json:
[
{"event_date":"<EVENT_DATE>","event_time":"<EVENT_TIME>","service":"api","event_type":"request","user_id":1001,"cost_ms":42,"success":true},
{"event_date":"<EVENT_DATE>","event_time":"<EVENT_TIME>","service":"worker","event_type":"job","user_id":1003,"cost_ms":900,"success":false}
]CLI 验证:
mkdir -p var/duckdb/tmp var/duckdb/out
duckdb var/duckdb/dev.duckdb "
SET temp_directory='var/duckdb/tmp';
CREATE OR REPLACE TABLE event_demo AS
SELECT *
FROM read_csv('dev-dependencies/duckdb/data/sample-events.csv', header=true, auto_detect=true);
SELECT service, event_type, count(*) AS total, round(avg(cost_ms), 2) AS avg_cost_ms
FROM event_demo
GROUP BY service, event_type
ORDER BY total DESC;
"聚合结果应稳定为:
api request 2 111.0
worker job 1 900.0这组数可以人工心算,所以它不仅证明 SQL 能执行,也能暴露列错位和类型漂移。随后执行 DESCRIBE event_demo,应看到 event_date、event_time、user_id、cost_ms 和 success 分别落到日期时间、整数和布尔类型;若推断结果与数据契约不同,不要继续导出。
检查 schema:
duckdb var/duckdb/dev.duckdb "DESCRIBE event_demo;"导出:
duckdb var/duckdb/dev.duckdb "
COPY (
SELECT service, event_type, count(*) AS total, avg(cost_ms) AS avg_cost_ms
FROM event_demo
GROUP BY service, event_type
)
TO 'var/duckdb/out/event-summary.parquet'
(FORMAT parquet);
"只读验证:
duckdb -readonly var/duckdb/dev.duckdb "SELECT count(*) AS rows FROM event_demo;"JSON 验证:
duckdb :memory: "
SELECT *
FROM read_json('dev-dependencies/duckdb/data/sample-events.json')
LIMIT 5;
"反向实验:让坏字段穿过抽样
CSV 本身不携带 schema,自动推断只能根据样本选择候选类型。准备 dev-dependencies/duckdb/data/bad-cost.csv:
event_date,cost_ms
<EVENT_DATE>,42
<EVENT_DATE>,180
<EVENT_DATE>,not-a-number先查看嗅探器生成的读取方案,再用契约中的显式类型读取:
SELECT Columns, Prompt
FROM sniff_csv('dev-dependencies/duckdb/data/bad-cost.csv');
SELECT *
FROM read_csv(
'dev-dependencies/duckdb/data/bad-cost.csv',
header=true,
columns={'event_date': 'DATE', 'cost_ms': 'INTEGER'}
);第二条查询应在第三行产生转换错误,而不是静默生成一个看似正常的汇总。定位坏值时不要改成全字符串后继续计算,可以显式读成字符串并使用 TRY_CAST:
SELECT *, TRY_CAST(cost_ms AS INTEGER) AS parsed_cost_ms
FROM read_csv(
'dev-dependencies/duckdb/data/bad-cost.csv',
header=true,
all_varchar=true
)
WHERE TRY_CAST(cost_ms AS INTEGER) IS NULL;预期只返回 not-a-number 那一行。生产校验应把“坏行数为 0”作为不变量;如果业务允许隔离坏行,则输出 rejects 制品并记录源文件、行号、错误类型和 owner,不能只统计成功行。
清理:
rm -rf var/duckdb/dev.duckdb var/duckdb/dev.duckdb.wal var/duckdb/dev.duckdb.tmp var/duckdb/tmp var/duckdb/outWindows PowerShell:
Remove-Item -LiteralPath "var/duckdb/dev.duckdb" -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath "var/duckdb/dev.duckdb.wal" -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath "var/duckdb/dev.duckdb.tmp" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath "var/duckdb/tmp" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath "var/duckdb/out" -Recurse -Force -ErrorAction SilentlyContinue共享环境不要把清理脚本写成无提示删除。至少要求 DUCKDB_ENV=local 或显式确认变量。
项目里建议保留:
dev-dependencies/duckdb/
.env.example
README.md
data/sample-events.csv
sql/verify.sql
sql/export-package.sql
scripts/verify-cli.sh
scripts/verify-python.py
scripts/clean-duckdb.sh
docs/duckdb-contract.mdsql/verify.sql:
SET temp_directory='var/duckdb/tmp';
CREATE OR REPLACE TABLE event_demo AS
SELECT *
FROM read_csv('dev-dependencies/duckdb/data/sample-events.csv', header=true, auto_detect=true);
SELECT
service,
event_type,
count(*) AS total,
round(avg(cost_ms), 2) AS avg_cost_ms
FROM event_demo
GROUP BY service, event_type
ORDER BY total DESC;执行:
duckdb var/duckdb/dev.duckdb < dev-dependencies/duckdb/sql/verify.sqlPython 项目接入:
import os
import duckdb
def open_duckdb(read_only: bool = False) -> duckdb.DuckDBPyConnection:
db_path = os.getenv("DUCKDB_PATH", "var/duckdb/dev.duckdb")
temp_dir = os.getenv("DUCKDB_TEMP_DIR", "var/duckdb/tmp").replace("'", "''")
con = duckdb.connect(db_path, read_only=read_only)
con.execute(f"SET temp_directory='{temp_dir}'")
return con
def verify_event_summary() -> list[tuple]:
con = open_duckdb()
try:
con.execute("""
CREATE OR REPLACE TABLE event_demo AS
SELECT *
FROM read_csv('dev-dependencies/duckdb/data/sample-events.csv', header=true, auto_detect=true)
""")
return con.execute("""
SELECT service, count(*) AS total
FROM event_demo
GROUP BY service
ORDER BY service
""").fetchall()
finally:
con.close()CI 接入原则:
每个并行任务使用独立 DUCKDB_PATH,例如 var/duckdb/${CI_JOB_ID}/dev.duckdb。:memory: 只用于单进程单测,不能作为跨步骤交付物。如果要读取交付 .duckdb 文件,使用 read-only 打开。
测试结束清理数据库文件、WAL、临时目录和导出文件。对输出数据做行数、checksum、关键聚合和 schema 验证。
数据交付包建议:
duckdb-package/
manifest.yml
schema.sql
generate.sql
validate.sql
data/
event-summary.parquet
checksums.txtmanifest.yml:
name: event-summary-demo
generated_at: "<GENERATED_TIMESTAMP>"
duckdb_version_policy: "project-locked"
source_data: "desensitized sample events"
contains_pii: false
outputs:
- path: data/event-summary.parquet
format: parquet
rows: 2
validation:
- validate.sql
owner: architecture-teamDuckDB 解决的核心架构问题
DuckDB 适合:
在开发机上用 SQL 快速分析 CSV / Parquet / JSON。用小样本复现数据问题,而不是把整套 OLAP 集群搬到本机。在 CI 中验证数据文件 schema、行数、关键聚合和导出格式。
给接口、报表、迁移脚本提供可复现 fixture。把本地分析结果交付为 Parquet / CSV / export 包。在 Python、Java、Node 脚本里做进程内数据检查。
DuckDB 不适合:
多人同时写同一个数据库文件。长期在线服务化查询。权限复杂、多租户、审计要求高的共享分析平台。
生产级数据仓库、Lakehouse、BI 后端和数据治理中台。高频在线事务、用户主库和强一致 OLTP 场景。
架构师判断重点不是“DuckDB 很轻”,而是问题是否真的只需要本地 SQL、单机文件、可复现样例和短生命周期制品。只要需求出现多人共享、权限隔离、长期存储、统一审计、在线 SLA、持续导入和资源隔离,就要转向 ClickHouse、Doris、PostgreSQL、对象存储加数据湖或正式数据平台。
主流落地形态
内存库单测
组成:测试进程、:memory: 连接、测试数据构造 SQL。
优点:快、隔离、不落盘。
问题:进程结束即消失,不能跨步骤复用,不能当交付物。
适用:函数级测试、SQL 语义验证、小数据聚合断言。
文件库项目缓存
组成:var/duckdb/dev.duckdb、WAL、临时目录、项目脚本。
优点:可重复打开,可保存中间表,适合本地分析和调试。
问题:文件锁、WAL、版本兼容、误提交和只读交付都要治理。
适用:开发机分析、迁移脚本验证、样例数据生成。
CLI 文件检查
组成:DuckDB CLI、CSV / Parquet / JSON、批处理 SQL。
优点:不依赖应用代码,适合排查和验收。
问题:相对路径、shell 差异、history 泄漏和输出目录污染。
适用:数据文件冒烟、PR 检查、制品验收。
CI fixture
组成:CI job、独立 DUCKDB_PATH、样例数据、golden query、清理脚本。
优点:把数据口径变成自动化验证。
问题:并行任务会互相污染,临时目录和 extension cache 可能越跑越大。
适用:数据导出、ETL 小样本、接口 mock 数据、报表回归。
只读数据包
组成:.duckdb 文件或 Parquet / CSV / export 包、manifest、schema、checksum、validate SQL。
优点:接收方可以复现查询,减少口头描述。
问题:.duckdb 文件可能较大,版本和写入模式要说明,敏感数据必须脱敏。
适用:架构评审样例、问题复现包、离线交付。
对象存储轻量校验
组成:httpfs extension、HTTP / S3 路径、临时凭证、只读查询。
优点:不必先下载全部文件,可直接做 schema 和聚合检查。
问题:扩展安装、代理、CA、权限、费用和凭证泄漏风险更高。
适用:临时检查对象存储样例文件,不替代正式数据平台治理。
核心机制
进程内执行
DuckDB 嵌入宿主进程。CLI、Python、Java、Node 本质都是在当前进程中打开数据库或文件。它没有天然的 server 账号、网络连接池和多租户边界,所以安全边界首先来自文件系统权限、进程隔离、只读打开、凭证注入和团队脚本。
:memory: 与文件库
:memory: 是临时数据库,适合单测和一次性 SQL。文件库会落成 .duckdb 文件,适合保存中间表和交付样例。把二者混淆会制造两类事故:测试以为有持久数据,重启后消失;交付以为只是临时文件,结果把大文件、WAL 和真实数据提交进仓库。
WAL、checkpoint 和临时目录
文件库读写过程中可能出现 WAL 和临时目录。复制或交付 .duckdb 文件前,最稳妥的做法是关闭连接,必要时执行 CHECKPOINT,再用 read-only 打开校验。大查询会 spill 到 temp_directory,CI 必须把它指向可清理路径。
外部文件扫描
DuckDB 可以直接读取 CSV、Parquet、JSON。Parquet 通常更适合稳定 schema 和列式分析;CSV / JSON 更容易遇到类型推断、日期、Decimal、NULL、编码和分隔符问题。自动推断只能做入门验证,关键数据要显式列类型、行数对账和聚合校验。
Extensions
Extensions 扩展 DuckDB 能力,例如 HTTP / S3、Excel、Spatial、FTS。团队最常见问题不是扩展不能用,而是本地能在线 INSTALL,CI 或内网不能下载。解决方式是记录 extension 名称、来源、版本策略、缓存目录和离线路径。
并发模型
原生 .duckdb 文件的稳定协作方式是:一个进程以 read-write 打开,或者多个进程全部以 read-only 打开。单个写进程内部可以创建多条连接并利用 MVCC 与乐观并发控制;append 通常不冲突,而两个线程更新同一行时,后到的事务会收到 conflict,需要在业务边界内重跑整个事务。
可以用两个终端验证文件锁。终端 A 执行 duckdb var/duckdb/lock-demo.duckdb 并保持会话;终端 B 再以 read-write 打开同一路径,应收到持锁进程信息或 lock error。关闭 A 后,同时启动两个 duckdb -readonly var/duckdb/lock-demo.duckdb,两边查询都应成功。CI 因此要给每个 job、worker 或 test shard 独立路径,不能用重试掩盖共享文件设计错误。
Quack 从 DuckDB 1.5.3 起提供远程协议,把一个 DuckDB 会话暴露为 HTTP client-server 入口;它仍是 beta / experimental,协议、函数和默认值都可能变化。服务端默认只绑定 localhost 并生成随机 token,但默认授权回调允许已认证客户端执行全部查询,非本机部署还需要反向代理终止 TLS,并自行收紧授权。DuckLake 1.0 则可用 PostgreSQL 等中心 catalog 协调多实例对共享数据的读写。两者都引入了独立协调组件、网络、鉴权、恢复和升级责任,不是原生文件锁的透明升级。只要目标是稳定的多人读写、统一鉴权和在线 SLA,就应把这些新增成本与 Doris、ClickHouse、PostgreSQL 或托管分析服务放在同一张选型表里评估。
Secrets 和历史文件
CLI history、SQL 文件、Notebook、持久 secrets、extension cache 都可能把凭证和路径留下来。不要因为 DuckDB 是本地工具就忽略凭证治理。对象存储访问要优先使用环境变量、临时凭证和最小权限。
DuckDB SQL 具有宿主进程权限,可以读文件、访问网络、安装扩展并消耗 CPU、内存和磁盘,不能执行不可信 SQL。只做数据库内查询的 CLI 验收可使用 duckdb -safe;它会关闭外部访问,因此 read_csv、COPY 和文件 ATTACH 也会被拒绝。嵌入式任务若只允许少数文件,应先设置 allowed_directories / allowed_paths 白名单,再关闭 enable_external_access;白名单是关闭外部访问后的例外,不是单独设置就能形成限制。随后禁用扩展自动安装和社区扩展,并用 SET lock_configuration=true 阻止后续放宽配置。这些设置是纵深防御,处理攻击者可控 SQL 时仍要使用低权限独立进程或容器、网络隔离和超时。
查看版本:
SELECT version();查看表:
SHOW TABLES;查看表结构:
DESCRIBE event_demo;直接读 CSV:
SELECT *
FROM read_csv('dev-dependencies/duckdb/data/sample-events.csv', header=true, auto_detect=true)
LIMIT 5;显式读 CSV:
SELECT *
FROM read_csv(
'dev-dependencies/duckdb/data/sample-events.csv',
header=true,
columns={
'event_date': 'DATE',
'event_time': 'TIMESTAMP',
'service': 'VARCHAR',
'event_type': 'VARCHAR',
'user_id': 'BIGINT',
'cost_ms': 'INTEGER',
'success': 'BOOLEAN'
}
);读 Parquet:
SELECT service, count(*) AS total
FROM read_parquet('var/duckdb/out/event-summary.parquet')
GROUP BY service;导出 Parquet:
COPY (
SELECT service, count(*) AS total
FROM event_demo
GROUP BY service
)
TO 'var/duckdb/out/service-summary.parquet'
(FORMAT parquet);导出 CSV:
COPY event_demo
TO 'var/duckdb/out/event-demo.csv'
(HEADER, FORMAT csv);导出数据库包:
EXPORT DATABASE 'var/duckdb/exported-package'
(FORMAT parquet);安装和加载扩展示意:
INSTALL httpfs;
LOAD httpfs;
SELECT extension_name, installed, loaded
FROM duckdb_extensions()
WHERE extension_name = 'httpfs';对象存储访问示意只保留占位符:
CREATE SECRET local_s3_demo (
TYPE s3,
KEY_ID 'YOUR_ACCESS_KEY_ID',
SECRET 'YOUR_SECRET_ACCESS_KEY',
REGION 'YOUR_REGION'
);这段只能作为语法边界示意。团队模板不要把真实 key 写进 SQL 文件,persistent secret 也要有清理和审计策略。
查看设置:
SELECT name, value
FROM duckdb_settings()
WHERE name IN ('threads', 'memory_limit', 'temp_directory', 'max_temp_directory_size');关闭前 checkpoint:
CHECKPOINT;命令找不到或版本漂移
现象:同事 A 能跑,CI 或同事 B 提示 duckdb: command not found,或者同一 SQL 在不同机器行为不一致。
判断:
which duckdb
duckdb -version修复:项目文档声明官方安装入口、版本锁定策略和验证命令。CI 可用 Docker 镜像或固定安装步骤,不依赖某台开发机。
相对路径漂移
现象:本地能读取 CSV,CI 提示文件不存在。
判断:
pwd
find . -maxdepth 4 -type f | sort | grep sample-events修复:脚本先切到项目根;路径使用 dev-dependencies/duckdb/... 或环境变量;不要在 SQL 里写个人绝对路径。
文件库被提交
现象:PR 出现 .duckdb、.wal、.tmp/、大 CSV 或导出 Parquet。
判断:
git status --short
git ls-files | grep -E "\\.duckdb|\\.wal|var/duckdb|\\.tmp/"修复:完善 .gitignore,大样例放制品库;交付包必须有 manifest 和审批。
:memory: 被误当持久库
现象:脚本第一步创建表,第二步新进程读取时报表不存在。
判断:检查连接字符串是否为 :memory:。
修复:单测用 :memory:;跨步骤、交付和多人读取使用显式文件路径。
并发写同一文件
现象:database is locked、写入失败、CI 偶发失败。
判断:检查并行任务是否共享 DUCKDB_PATH。
修复:一个 writer;多个 reader 使用 read-only;并行测试使用独立临时库。
大查询打满内存或磁盘
现象:OOM、CI runner 磁盘满、临时目录暴涨。
判断:
du -sh var/duckdb var/duckdb/tmp 2>/dev/null || trueSQL:
SELECT name, value
FROM duckdb_settings()
WHERE name IN ('memory_limit', 'temp_directory', 'max_temp_directory_size');修复:设置 memory_limit、temp_directory、max_temp_directory_size,减少扫描列和中间结果,测试后清理。
CSV / JSON 类型推断错误
现象:日期变字符串、Decimal 精度异常、NULL 被当字符串、布尔值不一致。
判断:
DESCRIBE SELECT *
FROM read_csv('dev-dependencies/duckdb/data/sample-events.csv', header=true, auto_detect=true);修复:关键数据显式列类型,保存 schema,做行数、最大最小值、关键聚合和 checksum 校验。
Extension 在线安装失败
现象:本地可 INSTALL httpfs,CI 或内网失败。
判断:看扩展下载日志、代理、CA、extension cache 和官方仓库可达性。
修复:预装或缓存扩展,记录来源和版本;内网使用受控镜像或本地 extension 路径。
Secrets 落盘或进 history
现象:SQL 文件、Notebook、.duckdb_history、stored_secrets、截图里出现 key。
判断:
rg -n "KEY_ID|SECRET|AWS_ACCESS_KEY|AWS_SECRET|CREATE SECRET|BEGIN .*PRIVATE KEY" .修复:用环境变量、临时凭证、最小权限;persistent secrets 只在有清理和审计策略时使用。
Parquet 口径差异
现象:DuckDB 校验通过,目标 ClickHouse / Doris / Spark 读取失败或类型不同。
判断:用目标引擎做最小冒烟,比较 schema、行数和关键聚合。
修复:DuckDB 做轻量校验,目标引擎仍保留最小验证。不要把 DuckDB 通过当成全平台兼容通过。
DuckDB 没有内置 server 账号体系。权限治理重点在文件系统、脚本、对象存储凭证和交付包。
治理原则:
数据文件目录只放脱敏样例。.env.example 只写占位符。DUCKDB_PATH 默认指向本地临时目录。
对外发送 .duckdb、Parquet、CSV 前必须脱敏和 checksum。对象存储凭证使用临时凭证和最小权限。SQL 文件、Notebook、CLI history、persistent secrets 都纳入敏感信息扫描。
read-only 是数据交付和多人读取的默认方式。
敏感信息扫描:
rg -n "DUCKDB_.*PASSWORD|KEY_ID|SECRET|AWS_ACCESS_KEY|AWS_SECRET|CREATE SECRET|BEGIN .*PRIVATE KEY|s3://|http://.*:.*@" .如果扫描命中的是占位符,要在安全审计记录里说明;如果是真实值,先撤出仓库和历史,再轮换凭证。
团队模板至少沉淀:
dev-dependencies/duckdb/README.md,写清入口、版本策略和清理规则。.env.example,只有占位符。sql/verify.sql,验证 CSV / Parquet / JSON、schema 和聚合。
scripts/verify-cli.sh 和 scripts/verify-python.py。scripts/clean-duckdb.sh / clean-duckdb.ps1。manifest/sample-package.yml,描述数据来源、脱敏、行数、schema、checksum 和 owner。
docs/duckdb-contract.md,写清什么时候用 DuckDB,什么时候转共享数据库或数据平台。
团队职责:
| 角色 | 负责 |
|---|---|
| 架构 / 数据 owner | DuckDB 使用边界、数据交付格式、schema、校验口径和替代方案判断 |
| 后端 / 脚本 owner | CLI / Python / Java / Node 接入、路径、依赖锁定和连接关闭 |
| 测试 owner | fixture、golden query、并行隔离、清理和 CI 断言 |
| 平台 / 运维 owner | CI runner 空间、离线 extension、对象存储网络和制品库 |
| 安全 owner | 样例脱敏、凭证、history、persistent secrets、导出包审查 |
团队最小工作流:
把 DuckDB 当服务是方向错误
如果需求开始出现端口、账号、权限、多人共享、长期在线查询、统一审计和资源隔离,就不该继续把 DuckDB 包装成服务。判断标准很简单:是否有多个独立用户同时访问同一份数据并要求权限隔离。如果有,优先转 ClickHouse、Doris、PostgreSQL、数据湖或托管分析服务。
相对路径会让脚本只在作者电脑上工作
DuckDB 经常直接读本地文件,路径问题比数据库问题更高发。CI 失败时先打印 pwd、列出样例文件、确认脚本是否从项目根运行。团队脚本统一先切项目根,或者用 DUCKDB_DATA_DIR 明确数据目录。
文件库、WAL 和临时目录会污染仓库
.duckdb 文件、WAL、.tmp/ 和导出 Parquet 都可能很大,也可能包含真实数据。PR 检查要扫描这些路径,.gitignore 要覆盖数据库文件、WAL 和临时目录。需要交付文件时走制品库,不走源码仓库。
:memory: 适合测试,不适合交付
:memory: 的优点就是不落盘。把它用于跨步骤流程,会导致表在新进程里消失;把它用于交付,会导致接收方无法复现。单测用 :memory:,可复现样例用显式 .duckdb 或 Parquet / export 包。
并发写同一文件会制造偶发锁失败
多个 CI worker、多个脚本、多个 Notebook 同时写一个 dev.duckdb,最容易出现偶发锁错误。解决不是重试十次,而是路径隔离:每个 worker 一个目录,一个 writer,读取用 read-only。
大查询不是免费本地计算
DuckDB 很容易让人把几 GB 文件直接拿来聚合。内存不够时会 spill 到临时目录,CI runner 磁盘可能被打满。团队模板要设置 memory_limit、temp_directory 和 max_temp_directory_size,并在失败时先看临时目录和输出文件大小。
CSV / JSON 自动推断只能做第一步
自动推断让入门很快,也容易把日期、Decimal、NULL、布尔值和编码读错。关键数据必须显式 schema,至少做 DESCRIBE、行数、空值数、最大最小值和关键聚合对账。对外输出前再用目标系统做一次最小验证。
Extension 不能假设随时在线安装
本机能 INSTALL httpfs,不代表内网、CI 和离线环境可用。扩展下载、缓存、版本和来源要写进模板。涉及 HTTP / S3 的任务还要确认代理、CA 证书、对象存储权限和出网成本。
Persistent secrets 不是安全保险箱
持久 secret 会落到本机目录。它能减少重复输入,但不是加密保险箱。真实生产凭证应优先走临时凭证、环境注入、最小权限和定期轮换;持久 secret 要有清理命令和敏感扫描。
CLI history 和 Notebook 会留下真实查询
开发者容易在 CLI 里粘贴对象存储 URL、token 或真实路径。.duckdb_history、Notebook 输出和截图都可能泄漏信息。交付前扫描 history、SQL、Notebook、Markdown 和图片素材。
只读交付要验证,不是口头说只读
交付 .duckdb 文件前,先关闭写连接,必要时 CHECKPOINT,再用 read-only 打开运行 validate.sql。如果接收方需要修改数据,说明 .duckdb 文件不是只读交付物,应转为明确的生成脚本和输出目录。
Parquet 交付通常比 .duckdb 文件更稳
.duckdb 文件适合完整复现,但更容易涉及版本、文件锁、体积和误写。跨团队交付优先考虑 Parquet / CSV / export 包,加 manifest、schema、checksum 和 validate SQL。只有需要保留 DuckDB 表、视图或多表关系时,才交付 .duckdb。
DuckDB 通过不代表目标引擎通过
DuckDB 很适合做轻量校验,但 ClickHouse、Doris、Spark、PostgreSQL 对类型、时区、Decimal、NULL 和 Parquet 细节可能不同。工具链里要保留目标引擎最小冒烟,尤其是数据最终会进入生产分析库时。
样例数据未脱敏是最高风险
本地分析最容易把真实日志、用户明细和订单数据复制到项目目录。检查入口包括字段名、样例行、导出文件、截图、manifest 和压缩包。没有脱敏脚本和字段说明的数据包,不允许进入仓库和公开文档。
数据交付不可复现会让排障回到口头沟通
只有一个 .duckdb 文件或 Parquet 文件,不足以支撑团队复现。必须附 schema、生成 SQL、源数据说明、DuckDB 版本策略、validate SQL、checksum 和 owner。否则下次出问题时没人知道这个文件怎么来的。
本机跑通:
CLI、语言包或镜像使用精确版本,并由锁文件和 SELECT version() 验证。项目已声明使用 CLI、Python、Java、Node 或 Docker 哪个主入口。duckdb :memory: "SELECT 1" 成功。
文件库 var/duckdb/dev.duckdb 可创建和读取。样例 CSV / Parquet / JSON 可读取。DESCRIBE 已检查 schema。
关键聚合有预期结果。可导出 Parquet 或 CSV。文件库可 read-only 打开。
清理脚本能删除数据库文件、WAL、临时目录和导出目录。
项目接入:
.env.example 只有占位符。DUCKDB_PATH、DUCKDB_TEMP_DIR、DUCKDB_EXTENSIONS_DIR 有默认值。Python / Java / Node 依赖有锁定策略。
脚本从项目根运行,不依赖个人绝对路径。CI 并行任务使用独立数据库路径。:memory: 只用于单进程测试。
read-only 用于交付包验收。输出文件有 manifest、schema、checksum 和 validate SQL。
架构判断:
已明确 DuckDB 不作为常驻 server。多人共享、权限隔离、长期在线查询需求已转向其他数据库或数据平台。CSV / JSON 自动推断不作为最终口径。
大查询有内存和临时目录限制。Extensions 有离线和缓存策略。目标引擎仍保留最小验证。
安全治理:
真实数据不进入 data/sample/。.duckdb、WAL、tmp、导出文件不进入源码仓库。SQL、Markdown、Notebook、CLI history 已做敏感信息扫描。
对象存储凭证使用临时凭证或环境变量。Persistent secrets 有清理和审计策略。对外交付包经过脱敏、checksum 和 owner 确认。
