DuckDB 是嵌入式分析数据库("SQLite for analytics"),单二进制,
能直接读 Parquet / CSV / JSON 文件,复杂分析查询比 pandas / Spark
本地模式快得多。
适合:本地数据探索、报表生成、数据科学家"我有 50GB Parquet 在 S3
想跑几条 SQL"。
安装
uv add duckdb
# 或独立 CLI:
curl https://install.duckdb.org | sh
duckdb --version
CLI 入门
duckdb my.db
# duckdb shell
D SELECT 1 + 1;
┌───────┐
│ ?col0 │
├───────┤
│ 2 │
└───────┘
D .help
D .quit
直接查 Parquet(不需要导入)
-- 单文件
SELECT * FROM 'data/sales.parquet' LIMIT 10;
-- 多文件
SELECT * FROM 'data/year=*/month=*/*.parquet' LIMIT 10;
-- S3 直接读
SET s3_region='us-east-1';
SET s3_access_key_id='...';
SET s3_secret_access_key='...';
SELECT count(*) FROM 's3://bucket/data/*.parquet';
year=*/month=* 是 Hive 风格分区路径,DuckDB 自动识别并 prune 不需要
的分区。
与 polars / pandas 互通
import duckdb
import polars as pl
# 直接查 polars DataFrame(不复制数据,Arrow zero-copy)
df = pl.read_csv('users.csv')
result = duckdb.sql("SELECT country, COUNT(*) FROM df GROUP BY 1").pl()
# 同理 pandas
import pandas as pd
pdf = pd.DataFrame({...})
result = duckdb.sql("SELECT ... FROM pdf").df()
duckdb.sql(...) 返回 DuckDBPyRelation,可以 .pl() .df() .fetchall()
转换。
典型用例
1. 探索一份大 CSV
import duckdb
duckdb.sql("""
SELECT
country,
COUNT(*) AS rows,
AVG(amount) AS avg_amt,
QUANTILE_CONT(amount, [0.5, 0.95, 0.99]) AS p50_p95_p99
FROM 'sales.csv'
WHERE date >= '2024-01-01'
GROUP BY country
ORDER BY rows DESC
LIMIT 20
""").show()
整个 CSV 在内存里只读一次,DuckDB 自动用所有 CPU 核心。
2. CSV → Parquet 转换
duckdb.sql("""
COPY (SELECT * FROM 'sales.csv') TO 'sales.parquet' (FORMAT PARQUET)
""")
# 同样一份数据,Parquet 通常 1/3 大小 + 列查询快 10x
按字段分区:
duckdb.sql("""
COPY (SELECT * FROM 'sales.csv')
TO 'partitioned/'
(FORMAT PARQUET, PARTITION_BY (year, month))
""")
# 输出:partitioned/year=2024/month=01/data_0.parquet ...
3. JOIN 多个文件
SELECT u.name, COUNT(*) AS n
FROM 'users.parquet' u
JOIN 'orders.parquet' o ON o.user_id = u.id
WHERE o.date >= '2024-01-01'
GROUP BY u.name
ORDER BY n DESC
LIMIT 10;
DuckDB 优化器自己决定 hash join / merge join;不需要建索引。
4. 窗口函数
SELECT
user_id,
date,
amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY date) AS cum_amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn
FROM 'orders.parquet'
WHERE rn = 1;
完整 SQL:2003 窗口函数。
5. 写回数据库
# 写到 Postgres
duckdb.sql("INSTALL postgres; LOAD postgres;")
duckdb.sql("""
ATTACH 'host=localhost dbname=mydb' AS pg (TYPE POSTGRES);
COPY (SELECT * FROM 'data.parquet') TO pg.public.my_table;
""")
DuckDB 与 SQLite 对比
| 维度 | SQLite | DuckDB |
|---|---|---|
| 设计 | OLTP(事务) | OLAP(分析) |
| 存储 | 行存 | 列存 |
| 多核 | 单线程 | 自动并行 |
| Parquet | 不直接支持 | 一等公民 |
| 文件 | .db 单文件 | .db 单文件,也能完全无文件 |
不要替代——它们解决不同问题。事务用 SQLite,分析用 DuckDB。
持久化 vs 内存
import duckdb
# 内存(默认)
con = duckdb.connect(':memory:')
# 文件
con = duckdb.connect('analytics.db')
文件模式可以 CREATE TABLE 持久化数据。内存模式纯查询。
大数据场景:spilling to disk
DuckDB 内存不够时自动 spill 到 tmp/:
duckdb.sql("PRAGMA temp_directory='./tmp_duckdb';")
duckdb.sql("PRAGMA memory_limit='4GB';")
跑大 query 不会 OOM,但慢点。比起 Spark 集群部署成本,单机能跑通是
胜利。
与 BI 工具集成
# Tableau / DBeaver / Metabase 都有 DuckDB connector
# JDBC: org.duckdb.DuckDBDriver
# ODBC: 官方提供
数据科学家用 DuckDB 算分析,BI 工具直接连展示。
CLI 高级技巧
# 用 fzf 选 .parquet 文件查
duckdb -c "SELECT * FROM '$(fd -e parquet | fzf)' LIMIT 100"
# 输出 JSON
duckdb -json -c "SELECT * FROM 'data.parquet' LIMIT 5"
# 输出 CSV
duckdb -c "COPY (SELECT * FROM 'data.parquet' WHERE x > 0) TO STDOUT WITH (HEADER, FORMAT CSV)"
踩过的坑
- 写 Parquet 时 string column 含 NULL → 默认 NULL handling 可能不对。
COPY ... TO ... (FORMAT PARQUET, COMPRESSION 'zstd')显式指定参数。 - S3 大量小文件(< 10MB / 文件)→ 元数据请求开销大。聚合成少量大文件
再扫。 - DuckDB 跨大版本(0.x → 1.x)数据库文件格式有时不兼容;升级前
EXPORT DATABASE '...'备份。 - python 包默认装的是 prebuilt wheel,没装某些 extension(spatial /
excel)。INSTALL spatial; LOAD spatial;运行时装。
登录后参与评论。