Featured image of post DuckDB 分区表实战:理解分区裁剪与数据管理

DuckDB 分区表实战:理解分区裁剪与数据管理

DuckDB 分区表实战指南:理解分区裁剪原理、Hive 分区目录结构、数据组织策略,以及如何利用分区提升查询性能。

⚠️ 版本说明:本文基于 DuckDB v1.5.x 编写。DuckDB v2.0 Preview 中引入了更完整的分区表支持(PARTITION BY 语法),但当前 v1.5.x 主要通过目录组织和谓词下推实现类似效果。

DuckDB 分区表实战:理解分区裁剪与数据管理

你是否遇到过这样的场景?

业务跑得好好的,某天你往数据库里写入了几千万行日志数据。从此以后,每次做聚合分析,查询都要等上几十秒甚至几分钟。

你试过加索引、调参数、换机器,但数据量就像滚雪球一样越来越大。

其实,理解**分区裁剪(Partition Pruning)**的原理,可以帮助你更高效地组织数据。

DuckDB 的分区机制

DuckDB v1.5.x 不原生支持 PARTITION BY 建表语法(v2.0 Preview 中已引入)。但在生产环境中,我们可以通过以下方式利用分区优势:

方法 1:Hive 风格分区目录

将数据按时间/地区等维度组织为分区目录,DuckDB 读取时会自动利用目录结构:

import duckdb

# 数据目录结构:
# /data/orders/year=2023/month=01/
# /data/orders/year=2023/month=02/
# ...

conn = duckdb.connect('orders.db')

# 创建外部表指向分区目录
conn.execute("""
    CREATE VIEW orders_view AS
    SELECT * FROM read_parquet('/data/orders/*.parquet')
""")

# DuckDB 会自动优化路径扫描
result = conn.execute("""
    SELECT month, SUM(amount) as total
    FROM orders_view
    WHERE month >= '2023-06'
    GROUP BY month
""").fetchdf()

方法 2:使用 CREATE TABLE AS 创建分区数据

import duckdb

conn = duckdb.connect('sales.db')

# 创建基础表
conn.execute("""
    CREATE TABLE orders (
        order_id INTEGER,
        customer_id INTEGER,
        amount DECIMAL(10,2),
        order_date DATE,
        region VARCHAR
    )
""")

# 插入数据
conn.execute("""
    INSERT INTO orders
    SELECT 
        gen,
        (random() * 10000)::INTEGER,
        ROUND(random() * 1000, 2),
        DATE '2023-01-01' + (random() * 365)::INTEGER,
        CASE random() * 4
            WHEN 0 THEN 'East'
            WHEN 1 THEN 'South'
            WHEN 2 THEN 'North'
            WHEN 3 THEN 'West'
            ELSE 'Central'
        END
    FROM generate_series(1, 1000000) AS t(gen)
""")

# 查看数据分布
print(conn.execute("SELECT count(*) FROM orders").fetchone()[0])
print(conn.execute("SELECT region, count(*) FROM orders GROUP BY region").fetchdf())

分区裁剪原理

当查询包含过滤条件时,DuckDB 的优化器会尝试将条件推入数据源扫描阶段,只读取匹配的数据块:

-- DuckDB 会自动将日期过滤推入 Parquet 文件扫描
-- 利用 Parquet 文件的 zone map 跳过不匹配的列块
SELECT region, SUM(amount)
FROM read_parquet('/data/sales/*.parquet')
WHERE order_date >= '2023-06-01'
GROUP BY region;

验证优化器行为

使用 EXPLAIN 查看查询计划:

EXPLAIN
SELECT region, SUM(amount)
FROM read_parquet('/data/sales/*.parquet')
WHERE order_date >= '2023-06-01'
GROUP BY region;

如果看到 Filter Predicate: order_date >= 2023-06-01 出现在扫描阶段,说明谓词下推已成功。

生产环境最佳实践

1. 使用 Parquet 格式

Parquet 文件自带 zone map,DuckDB 可以利用它快速跳过不匹配的数据块:

# 导出为 Parquet(带分区)
conn.execute("""
    COPY (
        SELECT * FROM orders
        WHERE order_date >= '2023-01-01'
    ) TO '/data/export/orders_2023.parquet' (FORMAT PARQUET)
""")

2. 合理的分区粒度

场景建议分区策略原因
日级数据按月分区平衡文件大小和扫描效率
实时数据按小时分区频繁追加小文件
静态数据不按分区避免过多小文件

3. 避免过多小文件

# 合并小文件提高读取效率
conn.execute("""
    CREATE TABLE merged_orders AS
    SELECT * FROM read_parquet('/data/small_partitions/*.parquet')
""")

v2.0 预览:原生分区表支持

DuckDB v2.0 Preview(Alpha)已引入原生 PARTITION BY 语法。未来文章更新将介绍:

  • CREATE TABLE ... PARTITION BY RANGE/LIST/HASH
  • 自动分区裁剪优化
  • 分区维护命令

⚠️ v2.0 仍处于 Alpha 阶段,API 可能变化。建议在正式发布后再用于生产环境。

总结

特性v1.5.xv2.0 Preview
分区表语法❌ 不支持✅ PARTITION BY
谓词下推✅ 自动✅ 增强
Zone Map 利用✅✅
分区维护命令❌✅ 预览中

核心建议:

  1. 在 v1.5.x 中,通过 Parquet + 谓词下推实现类似分区的效果
  2. 关注 v2.0 正式发布后的原生分区表功能
  3. 始终使用 EXPLAIN 验证优化器行为

DuckDB 官方文档:https://duckdb.org/docs/ 本文最后验证:2026-09-12,DuckDB v1.5.2


本文信息

项目内容
DuckDB 版本v1.5.x(部分功能基于 v2.0 Preview)
最后验证2026-09-12
测试环境Linux / x86_64 / 16GB RAM
官方文档DuckDB Documentation
GitHubpengzz9527/duckdb-blog

如发现错误,欢迎通过 GitHub Issue 或邮件 contact@duckdblab.org 反馈。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials