DuckDB 指南
你有没有遇到过这种情况:一个 2GB 的 CSV 文件,用 Pandas 读了 40 秒,内存直接飙到 8GB,风扇开始狂转。换成 DuckDB,3 秒搞定,内存几乎没动。
这不是夸张,这是 DuckDB 最直观的卖点——在你自己的电脑上,用 SQL 跑大数据分析,不用装任何数据库服务。
如果要用一句话概括 DuckDB,它就是数据分析界的 SQLite,一个随身携带的分析引擎。SQLite 解决了"我想在本地存点数据但不想装 MySQL"的问题,DuckDB 解决的是"我想在本地分析大数据但不想装 Spark"的问题。

#先说结论:DuckDB 适合谁
别浪费时间,先看看你是不是目标用户:
- 数据分析师:天天跟 CSV、Parquet 打交道,Pandas 写到怀疑人生的那种
- 数据工程师:需要在本地快速验证 ETL pipeline,不想每次都连远程数据库
- 后端开发者:想在应用里嵌入轻量分析能力,又不想维护一个独立的数据库服务
- 机器学习工程师:特征工程阶段需要对大量数据做聚合、筛选、join
- 学生和自学者:想学 SQL 但不想折腾 PostgreSQL 安装配置
如果你的日常工作是处理几百 MB 到几十 GB 的数据,而且经常在本地做分析,DuckDB 基本上是目前最好的选择。
#DuckDB 的核心能力
#1. 直接用 SQL 查询文件——这个功能真的会上瘾
我第一次用 DuckDB 替代 pd.read_csv 的时候,感觉就像从自行车换成了电动车——不是快一点点,是质的飞跃。当时有个 3GB 的用户日志,Pandas 读了快一分钟,DuckDB 两三秒就出结果了。从那以后我就再也回不去了。
不用先把数据导入数据库,直接对文件写 SQL:
sql-- 查询本地 CSV 文件 SELECT city, COUNT(*) as order_count, AVG(amount) as avg_amount FROM 'orders_2024.csv' WHERE status = 'completed' GROUP BY city ORDER BY order_count DESC LIMIT 10; -- 查询 Parquet 文件(列式存储,速度更快) SELECT * FROM 'user_events/*.parquet' WHERE event_date >= '2024-01-01'; -- 甚至可以直接查远程文件 SELECT * FROM 'https://example.com/data/sales.csv' LIMIT 100;
FROM 后面直接写文件路径,通配符、远程 URL、CSV / Parquet / JSON 都支持。头几次用的时候总觉得"不对吧,这也行?",但它就是行。
#2. 和 Python 无缝集成
说实话,我之前对"在 Python 里写 SQL"这件事是有偏见的——总觉得两种语言混着写很别扭。直到用了 DuckDB,才发现它做到了一种很舒服的共存:SQL 负责查询和聚合,Python 负责后续处理和画图,各干各的。
pythonimport duckdb import pandas as pd # 直接查询 Pandas DataFrame,不需要导入 df = pd.read_csv('users.csv') result = duckdb.sql("SELECT age, COUNT(*) FROM df WHERE country = 'AU' GROUP BY age") # 查询结果直接转回 DataFrame result_df = result.df() # 也可以转成 Polars DataFrame result_pl = result.pl() # 或者直接转成 Python 列表 rows = result.fetchall()
注意这里的关键:DuckDB 可以直接查询 Python 变量名。你定义了一个叫 df 的 DataFrame,SQL 里直接写 FROM df 就能用。不需要注册表、不需要创建连接,开箱即用。第一次见到这个设计的时候我愣了几秒——"等等,它怎么知道 df 是什么?"答案是 DuckDB 会自动扫描当前 Python 作用域里的变量。有点黑魔法,但确实好使。
#3. 列式存储:为什么分析场景下它碾压 SQLite
这部分稍微偏原理,不感兴趣可以跳过,但我觉得理解了之后会更有信心在生产环境使用它。
传统数据库(包括 SQLite)按行存储。跑 SELECT AVG(salary) FROM employees 时,它把每一行的所有列都读出来,哪怕你只关心 salary。DuckDB 是列式存储,同样的查询只读 salary 那一列,I/O 差了一个数量级。实测下来分析场景比 SQLite 快 10-100 倍不夸张。
打个比方:行式存储像翻一整本书找章节标题,列式存储像直接看目录。
#4. 向量化执行
DuckDB 处理数据不是一行一行来的,而是一批一批(通常 2048 行一批),能更好地利用 CPU 缓存和 SIMD 指令。具体细节不展开了,你只需要知道:这是它在分析查询上跑得快的另一个核心原因。

#真实使用场景
#场景一:本地大文件分析
公司给你一个 5GB 的用户行为日志 CSV,让你分析过去 30 天的活跃用户趋势。
用 Pandas 的痛苦:pd.read_csv() 先把 5GB 全部加载到内存,你的 16GB 笔记本直接卡死。就算用 chunksize 分块读取,代码写起来又臭又长。
用 DuckDB 的体验:
pythonimport duckdb result = duckdb.sql(""" SELECT DATE_TRUNC('day', event_time) as day, COUNT(DISTINCT user_id) as dau FROM 'user_events_202*.csv' WHERE event_time >= CURRENT_DATE - INTERVAL 30 DAY GROUP BY 1 ORDER BY 1 """).df() print(result)
几行代码,几秒出结果。DuckDB 在底下做了流式读取,不会一股脑把 5GB 全塞进内存。这种"看起来简单但底下做了很多事"的体验,是我喜欢 DuckDB 的主要原因。
#场景二:ETL 原型开发
这是我个人最高频的使用场景。写数据管道之前,总得在本地先跑通逻辑吧?以前要么连远程数据库(慢),要么本地装个 PostgreSQL(麻烦)。现在直接 DuckDB 搞定:
pythonimport duckdb con = duckdb.connect('prototype.duckdb') # 从不同格式的文件创建表 con.sql("CREATE TABLE orders AS SELECT * FROM 'orders.parquet'") con.sql("CREATE TABLE users AS SELECT * FROM 'users.csv'") con.sql("CREATE TABLE products AS SELECT * FROM 'products.json'") # 验证你的 ETL 逻辑 con.sql(""" CREATE TABLE order_summary AS SELECT u.country, p.category, DATE_TRUNC('month', o.created_at) as month, SUM(o.amount) as total_revenue, COUNT(DISTINCT o.user_id) as unique_buyers FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id GROUP BY 1, 2, 3 """) # 验证没问题后,导出结果 con.sql("COPY order_summary TO 'output.parquet' (FORMAT PARQUET)")
跑通了就迁移到 BigQuery 或 Snowflake,SQL 几乎不用改。省掉的不只是时间,还有心智负担。
#场景三:Notebook 里的数据探索
在 Jupyter 里做探索性分析的时候,窗口函数、CTE 这些操作用 Pandas 写总是很别扭,用 SQL 就自然多了:
python# 安装 JupySQL 扩展 # pip install jupysql duckdb-engine %load_ext sql %sql duckdb:// %%sql SELECT department, ROUND(AVG(salary), 2) as avg_salary, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median_salary, COUNT(*) as headcount FROM 'employees.csv' GROUP BY department HAVING COUNT(*) > 5 ORDER BY avg_salary DESC
你试试用 Pandas 写一个 PERCENTILE_CONT,再对比 SQL 这几行,就知道为什么越来越多人在 Notebook 里混着用 SQL 了。
#DuckDB vs Pandas vs SQLite 详细对比
| 对比维度 | DuckDB | Pandas | SQLite |
|---|---|---|---|
| 定位 | 嵌入式 OLAP 数据库 | 数据操作库 | 嵌入式 OLTP 数据库 |
| 查询语言 | SQL(兼容 PostgreSQL 语法) | Python API | SQL(语法较基础) |
| 存储方式 | 列式存储 | 内存中的 DataFrame | 行式存储 |
| 大文件处理 | 出色,流式读取不吃满内存 | 全量加载,内存容易爆 | 需要先导入,分析查询慢 |
| 多表 JOIN | 原生支持,优化器自动选执行计划 | merge() 写法不直观 | 支持,但大表性能差 |
| 窗口函数 | 完整支持 | 有但写法复杂 | 支持 |
| 并发写入 | 单写多读 | 不适用 | 单写多读 |
| 适合数据量 | MB 到几十 GB | MB 到几 GB(看内存) | KB 到几 GB |
| 学习成本 | 会 SQL 就能上手 | 需要学 Pandas API | 会 SQL 就能上手 |
| 生态集成 | Python, R, Node.js, Rust, Java | Python 为主 | 几乎所有语言 |
简单来说:分析聚合选 DuckDB,数据清洗选 Pandas,应用存储选 SQLite。三者不是互斥的——我自己最常见的工作流是 DuckDB 做完聚合,.df() 转成 Pandas 再用 matplotlib 画图。各司其职,配合得很好。
#最容易翻车的地方
#1. DuckDB 不适合做应用数据库
这点我要特别强调,因为我见过有人踩这个坑。DuckDB 是 OLAP(分析),不是 OLTP(事务)。别拿它替代 SQLite 或 PostgreSQL 存用户数据、处理并发写入——它的写入是单线程的,高并发直接出问题。
#2. 内存还是有上限的
DuckDB 内存管理确实比 Pandas 好一大截,但不是无限的。100GB+ 的数据集还是会遇到瓶颈,这时候老老实实上 Spark 或 BigQuery。
#3. 持久化数据库文件的坑——我自己也中过招
python# 内存模式(默认)——关掉就没了 con = duckdb.connect() # 持久化模式——数据存在文件里 con = duckdb.connect('my_data.duckdb')
我有一次花了大半个下午清洗数据、建好了几张中间表,结果忘了加文件路径,用的内存模式。关掉 Jupyter 的一瞬间人就傻了。从此之后养成习惯:只要不是写一次性脚本,第一行永远是 con = duckdb.connect('xxx.duckdb')。
#4. 版本兼容性
DuckDB 迭代很快,不同版本的持久化文件格式可能不兼容。升级版本后旧的 .duckdb 文件有可能打不开。所以重要数据务必同时导出一份 Parquet 备份,这个习惯能救命。
#5. 跨进程并发写入
多个 Python 进程同时写同一个 DuckDB 文件会直接报错。需要多进程写入的话,要么换 PostgreSQL,要么每个进程写自己的文件最后合并。这个限制不算 bug,是 DuckDB 作为嵌入式数据库的设计取舍。
#安装与快速上手
#安装
bash# Python(最常用) pip install duckdb # Node.js npm install duckdb # 命令行工具(macOS) brew install duckdb # 命令行工具(Linux) # 从 GitHub Releases 下载:https://github.com/duckdb/duckdb/releases
#30 秒快速体验
pythonimport duckdb # 1. 直接查询 CSV print(duckdb.sql("SELECT * FROM 'data.csv' LIMIT 5")) # 2. 做聚合分析 print(duckdb.sql(""" SELECT category, SUM(revenue) as total FROM 'sales.csv' GROUP BY category ORDER BY total DESC """)) # 3. 和 Pandas 配合 import pandas as pd df = pd.DataFrame({'name': ['Alice', 'Bob', 'Charlie'], 'score': [85, 92, 78]}) result = duckdb.sql("SELECT * FROM df WHERE score > 80").df() print(result) # 4. 导出结果到 Parquet duckdb.sql("COPY (SELECT * FROM 'raw_data.csv' WHERE amount > 100) TO 'filtered.parquet' (FORMAT PARQUET)")
#命令行使用
bash# 启动交互式 SQL 终端 duckdb # 直接对文件执行查询 duckdb -c "SELECT COUNT(*) FROM 'logs.csv'" # 打开已有的数据库文件 duckdb my_data.duckdb
#进阶技巧
#用 DuckDB 替代 pd.read_csv()
当 CSV 文件较大时,用 DuckDB 读取比 Pandas 快得多:
pythonimport duckdb # 比 pd.read_csv() 快 5-10 倍 df = duckdb.sql("SELECT * FROM 'large_file.csv'").df()
#查询远程 S3 文件
pythonimport duckdb con = duckdb.connect() con.sql("INSTALL httpfs; LOAD httpfs;") con.sql("SET s3_region = 'ap-southeast-2';") result = con.sql("SELECT * FROM 's3://my-bucket/data/*.parquet' LIMIT 100")
#多文件批量查询
sql-- 查询目录下所有 CSV SELECT * FROM 'data/2024/*.csv'; -- 带文件名列,知道每行来自哪个文件 SELECT filename, * FROM glob('data/2024/*.csv');
#相关资源
- Pandas 指南 — 数据清洗和逐行操作的首选工具
- Polars 指南 — 另一个高性能 DataFrame 库,和 DuckDB 互补
- Jupyter 指南 — 在 Notebook 中使用 DuckDB 的最佳搭档
- DuckDB 官网:https://duckdb.org↗
- DuckDB 文档:https://duckdb.org/docs/↗
- DuckDB GitHub:https://github.com/duckdb/duckdb↗
