⌘+k ctrl+k
1.3 (稳定版)
搜索快捷键 cmd + k | ctrl + k
文件格式

处理 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'});