首页/数据/数据工程与 ETL 速查/Parquet 列式文件格式
管道基础52 条

Parquet 列式文件格式

Parquet 是数据湖常用列式格式:自带 schema、按 row group 与 page 组织、每列保存统计信息,配合分区目录可大幅减少扫描量。

Parquet 是什么

Parquet 是面向数据分析的列式文件格式:数据按列而不是按行存放,同一列的值连续存储、类型一致,压缩效率远高于行式文件。它自带 schema 与列级统计信息,Spark、Hive、Trino、Flink 等引擎都能直接读写,是数据湖里落地明细层最常用的格式。

为什么它扫得快

查询只需要读到用到的列,其他列可以不落盘;每个 row group 与 page 都记录了 min/max 等统计值,引擎可以据此跳过整块不命中的数据。配合按日期分区的目录结构,一次查询可能只需要读全表几十分之一的数据。

基本信息

特点列式存储、自带 schema、列级统计、支持嵌套结构
粒度文件 → row group(常见 128MB 级)→ page
压缩列内数据同质,压缩率通常高于文本格式数倍
生态Spark、Hive、Trino、Flink、Presto 均可直接读写
写入方式通常不接受行级 UPDATE,改动需重写文件

典型使用场景

  • 数仓明细层(DWD)落地:列式读取 + 分区裁剪,报表扫描量最小
  • 日志明细入湖:字段多但每次只查少数字段,列式优势最明显
  • 需要保留 schema 的数据交换:自带类型信息,下游不必再猜字段类型
  • 冷数据长期归档:压缩率高,存储成本明显低于 JSON 或 CSV
  • 作为 Iceberg、Hudi 等表格式的底层文件格式使用

查看文件 schema 与统计信息

查看文件 schema 与统计信息
# 用 pyarrow 查看字段与类型
python -c "import pyarrow.parquet as pq; print(pq.read_schema('part-00000.parquet'))"

# 查看元数据:行数、row group 数量与每列大小
python -c "import pyarrow.parquet as pq; m=pq.ParquetFile('part-00000.parquet').metadata; print(m.num_rows, m.num_row_groups)"

Spark 按分区写入

Spark 按分区写入
# 按 dt 分区写出,查询时可直接按 dt 过滤,实现目录级裁剪
# maxRecordsPerFile 控制单个文件的行数,避免一次写出上千个小文件
df.write.mode('overwrite') \
  .partitionBy('dt') \
  .option('maxRecordsPerFile', 2000000) \
  .parquet('s3a://bucket/dwd/orders')

文件结构读法

概念含义常见取值
row group行组,列式统计与跳过的基本单位128MB 级,可调
page行组内的更小存储单元按列切分,受编码影响
列级统计每列的 min/max/null 计数用于谓词下推与数据跳过
footer文件末尾的 schema 与元数据读取时先解析 footer,收尾才能提交
单文件控制在百 MB 级、避免海量小文件,是扫描性能与写入成本的平衡点:文件太小会让元数据与文件打开开销占主导,太大则单个 task 并行度下降。
Parquet 不支持高效行级更新:频繁 UPDATE 会不断重写整个文件,需要 upsert 能力的场景应使用 Iceberg、Hudi 等表格式,或按分区整体覆盖。

易错点

  • 字段变更依赖 mergeSchema 自动合并:大目录下解析所有文件 footer 开销很高,应由写入方显式统一 schema
  • 对 Parquet 表做频繁行级更新,产生大量小文件与重复数据,扫描性能逐步劣化
  • 写入时不控制文件大小,一次作业生成上千个小文件,后续查询光打开文件就耗掉大半时间
  • 分区列与文件内字段重复存放(既在目录名里又在文件里),跨引擎读取时容易出现类型或取值不一致

数据来源与参考

  • Apache Parquet 官方文档
  • Apache Spark 官方文档:Parquet Files
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌