- 安装
- 文档
- 入门
- 连接
- 数据导入与导出
- 湖仓格式
- 客户端 API
- 概览
- 第三方客户端
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- ANALYZE
- ALTER TABLE
- ALTER VIEW
- ATTACH 和 DETACH
- CALL
- CHECKPOINT
- COMMENT ON
- COPY
- CREATE INDEX
- CREATE MACRO
- CREATE SCHEMA
- CREATE SECRET
- CREATE SEQUENCE
- CREATE TABLE
- CREATE VIEW
- CREATE TYPE
- DELETE
- DESCRIBE
- DROP
- EXPORT 和 IMPORT DATABASE
- INSERT
- LOAD / INSTALL
- MERGE INTO
- PIVOT
- 性能分析
- SELECT
- SET / RESET
- SET VARIABLE
- SHOW 与 SHOW DATABASES
- SUMMARIZE
- 事务管理
- UNPIVOT
- UPDATE
- USE
- VACUUM
- 查询语法
- SELECT
- FROM 和 JOIN
- WHERE
- GROUP BY
- GROUPING SETS
- HAVING
- ORDER BY
- LIMIT 和 OFFSET
- SAMPLE
- 展开嵌套
- WITH
- WINDOW
- QUALIFY
- VALUES
- FILTER
- 集合操作
- 预处理语句
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- Lance
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- Unity Catalog
- Vortex
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 概览
- DuckDB 的占用空间
- 安装 DuckDB
- 日志
- 保护 DuckDB 安全
- 非确定性行为
- 限制
- DuckDB Docker 容器
- 开发
- 内部结构
- 站点地图
- 在线演示
处理 Parquet 文件
DuckDB 对 Parquet 文件提供了高级支持,包括直接查询 Parquet 文件。在决定是直接查询这些文件还是先将其加载到数据库时,你需要考虑几个因素。
查询 Parquet 文件的理由
基本统计信息的可用性:Parquet 文件使用列式存储格式,并包含诸如区域映射 (zonemaps) 等基本统计信息。得益于这些特性,DuckDB 可以利用针对 Parquet 文件的投影和过滤下推等优化。因此,结合了投影、过滤和聚合的工作负载在 Parquet 文件上运行时往往表现良好。
存储考量:从 Parquet 文件加载数据将需要与 DuckDB 数据库文件大致相同的空间。因此,如果磁盘空间有限,直接对 Parquet 文件运行查询是值得的。
不建议查询 Parquet 文件的理由
缺乏高级统计信息:DuckDB 数据库格式具有 Parquet 文件所没有的 HyperLogLog 统计信息。这些信息提高了基数估计的准确性,如果查询包含大量的连接 (join) 操作,这一点尤为重要。
提示:如果你发现 DuckDB 在 Parquet 文件上生成的连接顺序不是最优的,请尝试将 Parquet 文件加载到 DuckDB 表中。改进后的统计信息很可能会帮助获得更好的连接顺序。
重复查询:如果你计划在同一个数据集上运行多次查询,那么将数据加载到 DuckDB 是值得的。查询速度总是会快一些,随着时间的推移,这可以摊销掉初始加载的时间。
解压时间长:一些 Parquet 文件使用 gzip 等重量级压缩算法进行压缩。在这种情况下,每次访问文件时查询 Parquet 文件都需要昂贵的解压时间。与此同时,轻量级压缩方法(如 Snappy、LZ4 和 zstd)的解压速度更快。你可以使用 parquet_metadata 函数来找出所使用的压缩算法。
微基准测试:在 DuckDB 数据库与 Parquet 上运行 TPC-H
TPC-H 基准测试中的查询在 Parquet 文件上的运行速度大约比在 DuckDB 数据库上慢 1.1-5.0 倍。
最佳实践:如果你有足够的存储空间,且工作负载包含大量连接和/或计划在同一数据集上运行多个查询,请先将 Parquet 文件加载到数据库中。Parquet 文件中的压缩算法和行组大小对性能影响很大:请使用
parquet_metadata函数来研究这些因素。
行组大小的影响
DuckDB 在行组大小为 10 万到 100 万行之间的 Parquet 文件上运行效果最佳。其原因是 DuckDB 只能跨行组进行并行处理——因此,如果一个 Parquet 文件只有一个巨大的行组,它只能由单个线程处理。你可以使用 parquet_metadata 函数来查看 Parquet 文件有多少个行组。在编写 Parquet 文件时,请使用 row_group_size 选项。
微基准测试:在不同行组大小下运行聚合查询
我们在不同行组大小(选择在 960 到 1,966,080 之间)的 Parquet 文件上运行了一个简单的聚合查询。结果如下。
| 行组大小 | 执行时间 |
|---|---|
| 960 | 8.77 秒 |
| 1920 | 8.95 秒 |
| 3840 | 4.33 秒 |
| 7680 | 2.35 秒 |
| 15360 | 1.58 秒 |
| 30720 | 1.17 秒 |
| 61440 | 0.94 秒 |
| 122880 | 0.87 秒 |
| 245760 | 0.93 秒 |
| 491520 | 0.95 秒 |
| 983040 | 0.97 秒 |
| 1966080 | 0.88 秒 |
结果表明,小于 5,000 的行组大小会产生强烈的负面影响,导致运行时间比理想行组大小长 5-10 倍以上,而 5,000 到 20,000 之间的行组大小距离最佳性能仍有 1.5-2.5 倍的差距。在行组大小超过 100,000 后,差异很小:最佳和最差运行时间之间的差距约为 10%。
Parquet 文件大小
DuckDB 也可以跨多个 Parquet 文件进行并行处理。建议所有文件的总行组数至少要与 CPU 线程数一样多。例如,在一台拥有 10 个线程的机器上,10 个各含 1 个行组的文件或 1 个含 10 个行组的文件都能实现完全并行。保持单个 Parquet 文件的大小适中也是有益的。
最佳实践:每个单独 Parquet 文件的理想大小范围在 100 MB 到 10 GB 之间。
用于过滤下推的 Hive 分区
当使用过滤条件查询许多文件时,可以通过使用 Hive 格式的文件夹结构沿过滤条件中使用的列对数据进行分区来提高性能。DuckDB 将只需要读取满足过滤条件的文件夹和文件。在查询远程文件时,这尤其有用。
关于读写 Parquet 文件的更多提示
有关读取和写入 Parquet 文件的提示,请参阅 Parquet 提示页面。
加载 CSV 文件
CSV 文件通常以压缩格式(如 GZIP 归档,即 .csv.gz)分发。DuckDB 可以即时解压这些文件。事实上,由于减少了 IO,这通常比先解压文件再加载它们要快。
| 模式 | 加载时间 |
|---|---|
从 GZIP 压缩的 CSV 文件(.csv.gz)加载 |
107.1 秒 |
解压(使用并行 gunzip)并从解压后的 CSV 文件加载 |
121.3 秒 |
加载大量小型 CSV 文件
CSV 读取器会对所有文件运行 CSV 嗅探器 (sniffer)。对于大量小文件,这可能会导致不必要的高开销。一种潜在的加速优化是关闭嗅探器。假设所有文件都具有相同的 CSV 方言和列名/类型,请按如下方式获取嗅探器选项
.mode line
SELECT Prompt FROM sniff_csv('part-0001.csv');
Prompt = FROM read_csv('file_path.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});
然后,你可以调整 read_csv 命令,例如应用 文件名扩展 (globbing),并使用嗅探器检测到的其余选项运行该命令
FROM read_csv('part-*.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={'hello': 'BIGINT', 'world': 'VARCHAR'});