起因
数据分析常见情境:
- 收到一堆 CSV / Parquet(几 GB - 几十 GB)
- 想跑 SQL JOIN / 聚合 / 窗口函数分析
- 没 Snowflake / BigQuery(个人项目 / 本地探索)
- pandas 慢 + groupby 写得难看
DuckDB:嵌入式 OLAP 数据库("SQLite for analytics"),单文件
binary,跑分析 SQL 跟 columnar 仓库一样快,在你笔记本上。
装
pip install duckdb # Python
brew install duckdb # CLI
CLI
$ duckdb my.db
D SELECT * FROM 'data.csv' LIMIT 5; -- 直接读 csv,无需 import
D SELECT COUNT(*) FROM 'data.parquet';
D SELECT a.x, b.y FROM 'a.csv' a JOIN 'b.parquet' b ON a.id = b.id;
CSV / Parquet / JSON 直接当 table 查,无 import 步骤。
Python
import duckdb
# in-memory
con = duckdb.connect()
# 直接查 CSV
df = con.execute("""
SELECT country, SUM(amount) AS total
FROM 'orders.csv'
WHERE qty > 5
GROUP BY country
ORDER BY total DESC
""").df() # 返回 pandas df
# 持久化
con = duckdb.connect('analysis.db')
con.execute("CREATE TABLE orders AS SELECT * FROM 'orders.csv'")
跟 pandas / DataFrame 互通
import pandas as pd
import duckdb
# pandas df 直接当 table 用(DuckDB zero-copy 引用)
df = pd.read_csv('big.csv')
result = duckdb.sql("""
SELECT col1, AVG(col2)
FROM df -- 直接引用 pandas df
GROUP BY col1
""").df()
polars 同样:
import polars as pl
pl_df = pl.read_csv('big.csv')
result = duckdb.sql("SELECT * FROM pl_df WHERE col1 > 100").pl()
DuckDB 跟 pandas / polars / Arrow 数据zero-copy 互转(都用 Arrow
columnar 内存格式)。
性能
8 核 16 GB 笔记本,10 GB Parquet 文件:
SELECT country, SUM(amount), COUNT(*)
FROM 'orders.parquet'
GROUP BY country
ORDER BY 2 DESC
LIMIT 10;
| 工具 | 时间 |
|---|---|
| pandas | 35s |
| polars (eager) | 8s |
| polars (lazy) | 4s |
| DuckDB | 2.5s |
DuckDB 列存 + vector 执行 + 多核 + 全局优化器,把分析查询打得很快。
直接查远程 Parquet
duckdb.sql("""
SELECT *
FROM read_parquet('s3://my-bucket/orders/*.parquet')
WHERE date = '2025-03-01'
""")
DuckDB 支持 S3 / GCS / Azure / HTTP 直读。
配合 partition + Parquet column pruning → 只读必要的 column 和 partition。
数据湖直接查
跟 Iceberg / Delta lake 集成:
INSTALL iceberg;
LOAD iceberg;
SELECT * FROM iceberg_scan('s3://bucket/table/');
不用 Spark 也能查 Iceberg。
window 函数 / 复杂 SQL
SELECT
user_id,
date,
amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY date) AS cum_sum,
RANK() OVER (PARTITION BY DATE_TRUNC('month', date) ORDER BY amount DESC) AS rank_in_month
FROM orders;
全 SQL 标准 + Postgres 兼容大量扩展 + DuckDB 特有的 ANTI/SEMI JOIN /
QUALIFY 等。
EXPORT / IMPORT
COPY (SELECT * FROM big_table) TO 'out.parquet' (FORMAT PARQUET);
COPY (SELECT * FROM big_table) TO 'out.csv' (HEADER, DELIMITER ',');
数据格式互转的瑞士军刀。
真实 case:替代 pandas EDA
数据探索,原本:
df = pd.read_csv('events.csv')
df_filtered = df[df.user_age > 18]
grouped = df_filtered.groupby(['country', 'product']).agg({
'amount': ['sum', 'mean'],
'qty': 'count',
}).reset_index()
sorted = grouped.sort_values(('amount', 'sum'), ascending=False)
sorted.head(20)
DuckDB 等价:
duckdb.sql("""
SELECT country, product,
SUM(amount) AS total,
AVG(amount) AS avg_amount,
COUNT(*) AS n
FROM 'events.csv'
WHERE user_age > 18
GROUP BY country, product
ORDER BY total DESC
LIMIT 20
""").df()
SQL 更直白 + 跑得快 + 不需要 import 完整 csv。
跟 Snowflake / BigQuery 对比
| DuckDB | Snowflake | BigQuery | |
|---|---|---|---|
| 部署 | 单 binary | SaaS | SaaS |
| 数据规模 | < 1 TB(单机) | PB | PB |
| 成本 | 0 | 按 credit | 按扫描 GB |
| 启动 | < 100ms | < 1s | < 5s |
| SQL | Postgres-like | ANSI++ | ANSI+ |
| 并发用户 | 单 | 多 | 多 |
DuckDB 不替代 Snowflake(不是 multi-user / 不是无限 scale)。
但 90% 个人 / 团队分析(< 1 TB)DuckDB 够 + 免费 + 快。
motherduck(DuckDB cloud)
DuckDB 团队也做了 motherduck.com → DuckDB + cloud sync:
- 本地查 + 云端永久存
- 共享数据集
- 跨设备一致
按需用,但 DuckDB 本身完全离线可用。
嵌入应用
# Django / FastAPI 里嵌 DuckDB 做 analytics endpoint
import duckdb
@app.get('/analytics/top-products')
def top_products():
return duckdb.sql("""
SELECT product, SUM(amount) AS total
FROM read_parquet('s3://.../orders/*.parquet')
WHERE date > current_date - 7
GROUP BY product
ORDER BY total DESC
LIMIT 10
""").df().to_dict('records')
不用 separate analytics DB / 全部嵌进应用。
extension 生态
INSTALL httpfs; -- HTTP / S3
INSTALL spatial; -- 地理空间
INSTALL fts; -- 全文搜索
INSTALL postgres; -- 查 Postgres 表
INSTALL excel; -- 读写 .xlsx
INSTALL sqlite; -- 读写 SQLite 文件
INSTALL postgres; LOAD postgres; 后:
ATTACH 'host=pg.example.com dbname=app user=...' AS pg (TYPE postgres);
SELECT * FROM pg.public.orders LIMIT 10;
把 Postgres 表当本地表查 + JOIN 本地 CSV → 异构数据查询。
踩过的坑
-
大数据 > RAM 时:DuckDB 用 disk spilling 但仍可能慢。
SET memory_limit='10GB',留剩余给 OS。 -
column type 自动推断错:CSV 列 sometime "N/A",DuckDB 推断
string。read_csv_auto(..., types={'col': 'INTEGER'})显式。 -
更新慢:DuckDB 是 OLAP,不适合频繁 UPDATE。点查 / 单行更新 →
用 SQLite。 -
并发写不行:单写者,多 reader。Web app 多 worker 同时写 DuckDB
会锁。 -
extension 版本不匹配:DuckDB 升级后 extension cache 旧版本
报错。FORCE INSTALL <ext>强制更新。
登录后参与评论。