- 安装
- 文档
- 入门
- 连接
- 数据导入
- 概览
- 数据源
- CSV 文件
- JSON 文件
- 多个文件
- Parquet 文件
- 分区
- Appender(追加器)
- INSERT 语句
- 客户端 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(加载/安装)
- PIVOT(透视)
- 性能分析
- SELECT(选择)
- SET / RESET(设置/重置)
- SET VARIABLE(设置变量)
- 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
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI(用户界面)
- 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
- 行为准则
- 发布日历
- 路线图
- 站点地图
- 在线演示
处理 Parquet 文件
DuckDB 对 Parquet 文件有高级支持,包括直接查询 Parquet 文件。在决定是直接查询这些文件还是先将它们加载到数据库时,您需要考虑几个因素。
查询 Parquet 文件的原因
基本统计信息的可用性: Parquet 文件使用列式存储格式,并包含诸如区域映射(zonemaps)等基本统计信息。得益于这些特性,DuckDB 可以利用对 Parquet 文件的投影和过滤下推等优化。因此,结合了投影、过滤和聚合的工作负载在 Parquet 文件上运行时通常表现良好。
存储考量: 从 Parquet 文件加载数据将需要与 DuckDB 数据库文件大致相同的空间。因此,如果可用磁盘空间受限,直接在 Parquet 文件上运行查询是值得的。
不查询 Parquet 文件的原因
缺乏高级统计信息: DuckDB 数据库格式拥有 Parquet 文件不具备的HyperLogLog 统计信息。这些统计信息提高了基数估计的准确性,并且在查询包含大量连接操作符时尤为重要。
提示: 如果您发现 DuckDB 在 Parquet 文件上产生了次优的连接顺序,请尝试将 Parquet 文件加载到 DuckDB 表中。改进的统计信息可能会有助于获得更好的连接顺序。
重复查询: 如果您计划对相同数据集运行多次查询,将数据加载到 DuckDB 中是值得的。查询将始终会更快一些,这样随着时间的推移,初始加载时间将得到摊销。
高解压时间: 某些 Parquet 文件使用重量级压缩算法(如 gzip)进行压缩。在这些情况下,查询 Parquet 文件每次访问时都需要耗费大量解压时间。同时,像 Snappy、LZ4 和 zstd 这样的轻量级压缩方法解压速度更快。您可以使用parquet_metadata
函数来找出使用的压缩算法。
微基准测试:在 DuckDB 数据库与 Parquet 上运行 TPC-H
在 Parquet 文件上运行TPC-H 基准测试的查询比在 DuckDB 数据库上慢约 1.1-5.0 倍。
最佳实践 如果您有可用的存储空间,并且工作负载包含大量连接操作和/或计划对相同数据集运行多次查询,请首先将 Parquet 文件加载到数据库中。Parquet 文件中的压缩算法和行组大小对性能有很大影响:使用
parquet_metadata
函数来研究这些因素。
行组大小的影响
DuckDB 在每个行组包含 100K-1M 行的 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 可以即时解压这些文件。事实上,由于减少了 I/O,这通常比先解压文件再加载它们更快。
模式 | 加载时间 |
---|---|
从 GZIP 压缩的 CSV 文件(.csv.gz )加载 |
107.1 秒 |
解压(使用并行 gunzip )并从解压后的 CSV 文件加载 |
121.3 秒 |
加载多个小型 CSV 文件
CSV 读取器会在所有文件上运行CSV 嗅探器。对于许多小型文件,这可能会导致不必要的高开销。一个潜在的优化是关闭嗅探器。假设所有文件都具有相同的 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'});