DuckDB 完全指南:从入门到精通

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 支持两种数据库模式:

  1. 持久化数据库 :数据保存在 .duckdb 文件中。关闭连接后数据不丢失。

    python 复制代码
    conn = duckdb.connect('my_database.duckdb')
  2. 内存数据库:数据仅在内存中,程序退出后消失,用于临时分析。

    python 复制代码
    conn = 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 命令快速了解数据概况:

    sql 复制代码
    SUMMARIZE SELECT * FROM large_table;

第五部分:总结与最佳实践

适用场景

  • 嵌入式分析:在 Python 应用内部进行复杂的数据聚合和清洗,替代 Pandas 的内存密集型操作。
  • 数据探索:在本地或 Jupyter Notebook 中分析 GB 级别的 CSV/Parquet 文件,无需搭建 Hadoop/Spark。
  • ETL 管道:作为轻量级的数据处理引擎,连接不同数据源(SQLite、S3、本地文件)进行转换。
  • 边缘计算:资源受限环境下需要运行 SQL 分析。

不适用场景

  • 高并发写入:DuckDB 是 OLAP 数据库,不适合作为在线交易(OLTP)系统的后端(如替代 PostgreSQL)。
  • 极大规模分布式计算:虽然支持多线程,但本质是单节点引擎,无法横向扩展到集群(除非结合 PySpark)。

核心原则

  1. 文件即表:能直接查询的文件就不要导入。
  2. 善用 Parquet:无论存储还是交换,Parquet 是最佳伴侣。
  3. 利用向量化:尽可能用 SQL 表达逻辑,避免逐行循环。
  4. 内存控制 :在内存紧张时设置 memory_limitexternal_sorting

DuckDB 正在迅速成为数据科学家和工程师的"新瑞士军刀"。它巧妙地填补了 Pandas 与分布式引擎(Spark)之间的空白,让我们在单机上就能高效处理"中等数据"(1GB 至 1TB)。通过本教程的学习,我们应该已经掌握了从基础查询到高级优化的全部技能,可以在实际项目中充分发挥 DuckDB 的威力。

相关推荐
暮冬-  Gentle°1 小时前
用Python批量处理Excel和CSV文件
jvm·数据库·python
m0_736914221 小时前
服务器上pip install spacy卡住解决方法
开发语言·python
小陈工1 小时前
2026年3月22日技术资讯洞察:数据库优化进入预测时代,网络安全威胁全面升级
java·开发语言·数据库·python·安全·web安全·django
m0_730115112 小时前
用户认证与授权:使用JWT保护你的API
jvm·数据库·python
kaisun642 小时前
树莓派4B上使用INMP441麦克风进行语音识别:从I2S配置到Python环境搭建全记录
python·语音识别·树莓派
七夜zippoe2 小时前
Python 3.12+ 新特性深度解析:类型系统与性能革命
android·网络·python·类型系统·性能革命·3.12+
如若1232 小时前
WSL2 启动报错“拒绝访问“ E_ACCESSDENIED 完整解决方案
人工智能·pytorch·python·深度学习·计算机视觉
qq_334903152 小时前
用Python实现自动化的Web测试(Selenium)
jvm·数据库·python
Storynone2 小时前
【Day30】卡码网:46. 携带研究材料,LeetCode:416. 分割等和子集
python·算法·leetcode