DuckDB 完全指南:从入门到精通
第一部分:入门篇
1.1 什么是 DuckDB?
DuckDB 是一个嵌入式、进程内、列式存储 的 SQL 数据库管理系统(DBMS)。它的设计目标是提供一种高性能、易于使用且零配置的数据分析解决方案。
核心特点:
- 嵌入式:没有独立的服务器进程,直接在应用程序(如 Python、R、Node.js)中运行。
- 列式存储:专为分析型查询(OLAP)优化,在聚合、扫描大量数据时性能远超传统行式数据库(如 SQLite)。
- 零配置 :无需安装、启动服务或创建用户。
pip install duckdb即可使用。 - SQL 支持:支持标准 SQL,并扩展了针对分析的强大功能(如窗口函数、列表/结构体操作、JSON 处理)。
- 与数据生态深度集成:可以直接读取/写入 CSV、Parquet、JSON 文件,甚至可以"直接查询"这些文件,无需先导入表。
1.2 安装与连接
Python 环境(最常用)
bash
pip install duckdb
CLI 环境(命令行工具)
下载官方提供的 duckdb 可执行文件(单文件),放到系统路径后即可运行:
bash
duckdb
# 或打开指定数据库文件
duckdb mydb.duckdb
其他语言:支持 R、Java、Node.js、C++ 等,语法类似。
1.3 第一个查询
在 Python 中:
python
import duckdb
# 1. 内存模式(不持久化)
conn = duckdb.connect(':memory:')
# 2. 执行 SQL
result = conn.execute("SELECT 1 + 1").fetchall()
print(result) # [(2,)]
# 或使用快捷方式(自动管理连接)
print(duckdb.sql("SELECT 'Hello, DuckDB!' as greeting").fetchone())
在命令行中:
sql
SELECT 'Hello, DuckDB!' as greeting;
第二部分:核心概念与基础操作
2.1 数据库与连接
DuckDB 支持两种数据库模式:
-
持久化数据库 :数据保存在
.duckdb文件中。关闭连接后数据不丢失。pythonconn = duckdb.connect('my_database.duckdb') -
内存数据库:数据仅在内存中,程序退出后消失,用于临时分析。
pythonconn = duckdb.connect(':memory:')
2.2 创建表与导入数据
创建表
python
conn.execute("CREATE TABLE users (id INTEGER, name VARCHAR, age INTEGER)")
# 插入数据
conn.execute("INSERT INTO users VALUES (1, 'Alice', 30), (2, 'Bob', 25)")
直接从 CSV 创建表
python
conn.execute("""
CREATE TABLE customers AS
SELECT * FROM read_csv_auto('customers.csv')
""")
注册 Pandas DataFrame
将 Pandas DataFrame 注册为虚拟表,可以直接查询:
python
import pandas as pd
df = pd.DataFrame({'id': [1, 2], 'value': [10, 20]})
conn.register('my_df_view', df)
result = conn.execute("SELECT * FROM my_df_view WHERE value > 10").fetchdf()
2.3 查询 CSV/Parquet 文件(无需导入)
这是 DuckDB 的杀手锏 特性:可以直接在文件上进行 SQL 查询,无需 CREATE TABLE。
python
# 直接查询 CSV
duckdb.sql("""
SELECT PassengerId, AVG(Fare)
FROM read_csv_auto('titanic.csv')
GROUP BY PassengerId
""")
# 直接查询 Parquet(性能极高)
duckdb.sql("""
SELECT COUNT(*), PULocationID
FROM read_parquet('yellow_tripdata_2021-01.parquet')
GROUP BY PULocationID
""")
第三部分:进阶篇
3.1 高级 SQL 特性
DuckDB 的 SQL 引擎非常强大,支持现代数据分析所需的几乎所有语法。
窗口函数
sql
SELECT
id,
sales,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY sales DESC) as rank_in_region,
SUM(sales) OVER (PARTITION BY region) as total_sales_in_region
FROM sales_data;
公共表表达式 (CTE)
sql
WITH high_value_orders AS (
SELECT * FROM orders WHERE amount > 1000
)
SELECT customer_id, COUNT(*)
FROM high_value_orders
GROUP BY customer_id;
处理嵌套数据(Struct 和 List)
DuckDB 原生支持复杂类型:
python
# 创建包含 List 的表
duckdb.sql("""
CREATE TABLE orders (
id INT,
items VARCHAR[] -- 数组类型
);
INSERT INTO orders VALUES (1, ['apple', 'banana']);
""")
# 展开数组(UNNEST)
duckdb.sql("SELECT id, UNNEST(items) FROM orders").show()
JSON 支持
DuckDB 对 JSON 的处理性能极高,甚至可以超过专门的 JSON 工具。
sql
-- 直接从 JSON 文件查询
SELECT
json_extract_string(data, '$.name') as name,
json_extract_int(data, '$.age') as age
FROM read_json_auto('people.json');
-- 使用 -> 和 ->> 操作符(类似 PostgreSQL)
SELECT data->>'name' FROM people;
3.2 数据导入与导出
DuckDB 提供了多种高效的数据交换方式。
导出到 Parquet(推荐)
python
conn.execute("""
COPY (SELECT * FROM large_table)
TO 'output.parquet'
(FORMAT PARQUET);
""")
导出到 CSV
python
conn.execute("""
COPY users TO 'users.csv' (HEADER, DELIMITER ',');
""")
从多种格式导入
python
# 支持 CSV, JSON, Parquet, Arrow, Pandas, SQLite 等
conn.execute("CREATE TABLE t1 AS SELECT * FROM 'data.parquet'")
conn.execute("CREATE TABLE t2 AS SELECT * FROM 'data.csv'")
3.3 与 Python 生态无缝集成
DuckDB 可以高效地在 Pandas、Polars、Arrow 之间传递数据,实现"零拷贝"交互。
python
import pandas as pd
import polars as pl
# 查询返回 Pandas DataFrame
df = duckdb.sql("SELECT * FROM 'sales.parquet'").df()
# 查询返回 Polars DataFrame
df_pl = duckdb.sql("SELECT * FROM 'sales.parquet'").pl()
# 查询返回 PyArrow Table
arrow_table = duckdb.sql("SELECT * FROM 'sales.parquet'").arrow()
# 直接操作 Pandas DataFrame(无需复制)
pandas_df = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6]})
result = duckdb.sql("SELECT a, SUM(b) FROM pandas_df GROUP BY a").df()
第四部分:精通篇
4.1 架构与性能优化
列式存储原理
DuckDB 按列存储数据,在执行聚合查询时,只需读取相关列,大幅减少 I/O。同时利用向量化执行引擎(一次处理 2048 行),提高 CPU 缓存效率。
分区与分区剪裁
对于大表,按时间或关键字段分区可以显著提升查询速度。
sql
-- 创建分区表
CREATE TABLE events (id INT, event_date DATE, data VARCHAR)
PARTITION BY (event_date);
-- 查询时只扫描相关分区
SELECT * FROM events WHERE event_date = '2023-01-01';
索引
虽然 DuckDB 在大多数分析场景中依赖分区和文件统计信息,但也支持创建索引:
sql
CREATE INDEX idx_id ON users (id);
查询分析(EXPLAIN)
使用 EXPLAIN 查看查询计划,识别瓶颈:
sql
EXPLAIN SELECT COUNT(*) FROM large_table WHERE column > 100;
4.2 内存管理
DuckDB 允许用户控制内存使用量,防止 OOM(内存溢出)。
python
# 设置最大内存为 4GB
conn.execute("SET memory_limit = '4GB'")
# 启用外部排序,当内存不足时使用磁盘
conn.execute("SET enable_external_sorting = true")
4.3 扩展系统
DuckDB 支持动态加载扩展,以增强功能。
常用扩展:
-
httpfs:支持直接查询 HTTP/HTTPS 上的文件(如 S3、公共数据集)。sql
INSTALL httpfs;
LOAD httpfs;
SELECT * FROM read_parquet('https://example.com/data.parquet');
- **`json`**:增强 JSON 处理(默认已加载)。
- **`sqlite`**:直接读写 SQLite 数据库文件。
```sql
INSTALL sqlite;
LOAD sqlite;
ATTACH 'old.db' AS sqlite_db (TYPE SQLITE);
SELECT * FROM sqlite_db.users;
mysql/postgres:直接连接外部数据库进行联合查询。
4.4 实战:构建数据管道
下面是一个典型的 ETL 场景,展示了 DuckDB 的简洁与高效。
场景:从 S3 读取每日日志(Parquet),进行清洗聚合,将结果写入本地数据库。
python
import duckdb
conn = duckdb.connect('analytics.duckdb')
# 加载 httpfs 扩展以访问 S3(需要配置凭证)
conn.execute("INSTALL httpfs; LOAD httpfs;")
conn.execute("SET s3_region = 'us-east-1';")
# 执行复杂的 ETL 流程
conn.execute("""
CREATE OR REPLACE TABLE daily_agg AS
WITH raw_data AS (
-- 直接查询 S3 上的多个 Parquet 文件
SELECT * FROM read_parquet('s3://my-bucket/logs/date=2023-*/*.parquet')
WHERE user_id IS NOT NULL -- 数据清洗
),
enriched AS (
SELECT
u.user_id,
u.region,
COUNT(r.event_id) AS event_count,
SUM(r.revenue) AS total_revenue
FROM raw_data r
JOIN users u ON r.user_id = u.id
GROUP BY u.user_id, u.region
)
SELECT * FROM enriched;
""")
# 导出结果到 CSV
conn.execute("COPY daily_agg TO 'daily_report.csv' (HEADER);")
4.5 性能对比与调优
| 场景 | 传统方案 (Pandas) | DuckDB 方案 | 优势 |
|---|---|---|---|
| 读取 10GB CSV 并聚合 | 容易内存溢出,慢 | SELECT col, COUNT(*) FROM 'file.csv' GROUP BY col |
列式扫描,内存友好,速度快 5-20 倍 |
| 多表 Join | 需手动 merge,内存开销大 | SQL Join,自动优化 | 代码简洁,性能高 |
| 处理嵌套 JSON | 使用 json_normalize 繁琐 |
read_json_auto() 自动展开 |
开发效率高 |
调优建议:
-
尽量使用 Parquet 作为数据源,其列式存储和压缩特性与 DuckDB 天生契合。
-
对于超大文件,使用
read_parquet()时利用filename字段或分区列进行过滤。 -
使用
SUMMARIZE命令快速了解数据概况:sqlSUMMARIZE SELECT * FROM large_table;
第五部分:总结与最佳实践
适用场景
- 嵌入式分析:在 Python 应用内部进行复杂的数据聚合和清洗,替代 Pandas 的内存密集型操作。
- 数据探索:在本地或 Jupyter Notebook 中分析 GB 级别的 CSV/Parquet 文件,无需搭建 Hadoop/Spark。
- ETL 管道:作为轻量级的数据处理引擎,连接不同数据源(SQLite、S3、本地文件)进行转换。
- 边缘计算:资源受限环境下需要运行 SQL 分析。
不适用场景
- 高并发写入:DuckDB 是 OLAP 数据库,不适合作为在线交易(OLTP)系统的后端(如替代 PostgreSQL)。
- 极大规模分布式计算:虽然支持多线程,但本质是单节点引擎,无法横向扩展到集群(除非结合 PySpark)。
核心原则
- 文件即表:能直接查询的文件就不要导入。
- 善用 Parquet:无论存储还是交换,Parquet 是最佳伴侣。
- 利用向量化:尽可能用 SQL 表达逻辑,避免逐行循环。
- 内存控制 :在内存紧张时设置
memory_limit和external_sorting。
DuckDB 正在迅速成为数据科学家和工程师的"新瑞士军刀"。它巧妙地填补了 Pandas 与分布式引擎(Spark)之间的空白,让我们在单机上就能高效处理"中等数据"(1GB 至 1TB)。通过本教程的学习,我们应该已经掌握了从基础查询到高级优化的全部技能,可以在实际项目中充分发挥 DuckDB 的威力。