Parquet vs ORC 文件格式:分析场景下的压缩比与查询性能对比

📅 发布时间:2026/7/18 22:35:39
Parquet vs ORC 文件格式:分析场景下的压缩比与查询性能对比 Parquet vs ORC 文件格式分析场景下的压缩比与查询性能对比一、选格式的纠结每个数据仓库人都经历过建表的时候总会遇到一个选择题用 Parquet 还是 ORC两个都是列式存储格式都支持压缩都有谓词下推——看起来半斤八两。但真正跑起查询来差别还是挺明显的。这篇我们不说厂商的 benchmark直接拿真实数据做对比测试看看到底该怎么选。flowchart TB subgraph 存储格式对比 A[原始 CSV 数据br/10GB] A -- B[Parquet Snappy] A -- C[ORC Zlib] A -- D[Parquet Zstd] A -- E[ORC Snappy] end subgraph 关键维度 F[压缩比] G[全表扫描速度] H[列裁剪查询] I[谓词下推效率] end B -- F G H I C -- F G H I D -- F G H I E -- F G H I二、文件结构差异根本原因在这Parquet 和 ORC 都是列式存储 Stripe 结构但在具体实现上有几个关键差异。Parquet 的文件组织文件 ├── Row Group 1按行切分的一组数据通常 128MB │ ├── Column Chunk: user_id 列的数据块 │ │ ├── Data Page 1 真正存储列值的地方 │ │ ├── Data Page 2 │ │ └── Dictionary Page 字典编码加速页 │ ├── Column Chunk: amount │ └── Column Chunk: create_time ├── Row Group 2 │ └── ... └── Footer元数据Schema、Row Group 偏移量、统计信息 ├── min/max per column 谓词下推就靠它 ├── null_count └── distinct_count可选ORC 的文件组织文件 ├── Stripe 1约 64MB比 Parquet 的 Row Group 小一半 │ ├── Index Data每 10000 行一个索引条目比 Parquet 更细粒度 │ ├── Row Data列数据 │ │ ├── Stream: user_id 列流数据 长度 字典 │ │ └── Stream: amount │ └── Stripe FooterStripe 级别的统计信息 ├── Stripe 2 │ └── ... ├── File Footer全局元数据 └── Postscript文件尾部标识核心差异ORC 的 Stripe 默认 64MBParquet 的 Row Group 默认 128MB。更小的 Stripe 意味着索引更密集对于点查和谓词过滤更友好但元数据开销更大。ORC 的轻量级索引Bloom Filter Row Index比 Parquet 的 Column Statistics 更细粒度在跳过不相关的数据块时效果更好。Parquet 的嵌套结构支持Nested Types是原生设计对 JSON、Protobuf 这类嵌套数据天然友好。ORC 也支持但做起来更别扭。三、压缩比实测同样是列存压缩率能差 30%测试数据dwd_order_detail_di表2026年6月全量约 1200 万行原始 CSV 大小 3.2GB。from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(FormatBenchmark) \ .config(spark.sql.parquet.compression.codec, snappy) \ .getOrCreate() # 读取原始 Parquet 数据 df spark.read.parquet(/data/benchmark/source/) # 写入不同格式各存一份对比 formats { parquet_snappy: (parquet, snappy), parquet_zstd: (parquet, zstd), parquet_gzip: (parquet, gzip), orc_snappy: (orc, snappy), orc_zlib: (orc, zlib), } for label, (fmt, codec) in formats.items(): output_path f/data/benchmark/output/{label}/ df.write \ .format(fmt) \ .option(compression, codec) \ .mode(overwrite) \ .save(output_path) # 用 Hadoop fs -du 看各格式实际占用空间实测结果1200 万行订单数据格式压缩算法文件大小压缩比相对 CSVCSV原始无3.20 GB1.00xParquetSnappy642 MB5.10xParquetZstd (level 3)487 MB6.72xParquetGzip452 MB7.24xORCSnappy578 MB5.67xORCZlib423 MB7.74x结论ORC Zlib 组合压缩比最高7.74x比 Parquet Snappy 多省了约 34% 的存储空间。主要原因是 ORC 的轻量级索引让它能用更激进的字典编码和 RLERun-Length Encoding而不影响查询速度。但要注意压缩比和查询速度是互斥的Zlib/Gzip 压缩比高但解压慢做全表扫描时比 Snappy 慢 2~3 倍。为什么压缩比和查询速度互斥但 Snappy 能成为默认选择Zlib 的压缩算法是 LZ77 Huffman它需要通过滑动窗口查找重复模式来压缩数据解压时要重建这些模式——这个过程 CPU 密集。Snappy 的设计哲学是不追求极致压缩但解压速度接近内存拷贝。在数据仓库查询场景下每一次 SELECT 都可能扫描几十 GB 的数据这些数据要先解压才能过滤。Zlib 多花 3 秒解压 × 每天 5000 次查询 每天多烧 4 小时的 CPU 时间。除非你的存储账单比计算账单贵 3 倍以上否则 Snappy 的速度优势通常碾压 Zlib 的存储优势。四、查询性能实测场景决定胜负import time def benchmark_query(df, query_name: str): 简单性能测试记录查询耗时 start time.time() count df.count() elapsed time.time() - start print(f[{query_name}] 行数: {count}, 耗时: {elapsed:.2f}s) return elapsed # 场景1全表聚合SELECT COUNT(*), SUM(amount) df_parquet spark.read.parquet(/data/benchmark/output/parquet_snappy/) df_orc spark.read.orc(/data/benchmark/output/orc_snappy/) # 触发全表扫描 benchmark_query(df_parquet.agg({amount: sum}), Parquet全表聚合) benchmark_query(df_orc.agg({amount: sum}), ORC全表聚合) # 场景2列投影只需读 3 列表有 35 列 df_parquet_col spark.read.parquet(/data/benchmark/output/parquet_snappy/) \ .select(order_id, user_id, amount) df_orc_col spark.read.orc(/data/benchmark/output/orc_snappy/) \ .select(order_id, user_id, amount) benchmark_query(df_parquet_col, Parquet列投影) benchmark_query(df_orc_col, ORC列投影) # 场景3谓词过滤查找特定城市 金额 1000 from pyspark.sql.functions import col df_parquet_filtered spark.read.parquet(/data/benchmark/output/parquet_snappy/) \ .filter((col(city) 深圳) (col(amount) 1000)) df_orc_filtered spark.read.orc(/data/benchmark/output/orc_snappy/) \ .filter((col(city) 深圳) (col(amount) 1000)) benchmark_query(df_parquet_filtered, Parquet谓词过滤) benchmark_query(df_orc_filtered, ORC谓词过滤)实测性能数据在三节点 64 核 Spark 集群上单位秒查询场景Parquet (Snappy)ORC (Snappy)胜者全表 COUNT(*)8.2s6.7sORC 快 18%全表 SUM(amount)11.3s8.9sORC 快 21%列投影3/35列2.1s2.5sParquet 快 16%谓词过滤筛选特定城市5.8s2.4sORC 快 59%嵌套字段查询3.1s4.8sParquet 快 35%逐项分析全表扫描ORC 胜。ORC 的 Stripe 更小扫描粒度更细轻量级索引在分区内过滤效果更好。列投影Parquet 胜。Parquet 的 Column Chunk 页面对齐做得更好按列读取时磁盘寻道更少。谓词过滤ORC 完胜快 59%。ORC 的 Stripe-level 索引 Bloom Filter 能在不读实际数据的情况下跳过 90% 的不相关 Stripe。嵌套数据Parquet 胜。这是 Parquet 的看家本领Dremel 编码对嵌套结构的列裁剪是天生的。为什么嵌套数据查询 Parquet 快 35% 不是小事如果你有一个order_detail表其中items字段是一个 ArrayStructsku_id, price, quantity 的嵌套结构。查询SELECT items.sku_id FROM orders时Parquet 的 Dremel 编码可以直接跳到 items.sku_id 列的 Column Chunk只解压这一个嵌套列。但 ORC 需要先解压整个 Stripe 的 Row Data找到 items 的所有 Streamdata length dictionary然后在 Stream 内部定位 sku_id 字段。嵌套层次越多ArrStructArr...ORC 的解压浪费就越大。如果你的数据模型大量使用嵌套结构如日志系统、IoT 数据Parquet 的这个优势不是 35%而是 3-5 倍。五、总结 踩坑提醒ORC Gzip 组合解压耗时远超查询本身文章里实测 ORCSnappy 全表聚合 8.9 秒但如果换成 ORCGzip解压缩的 CPU 时间会飙升到 25-30 秒查询本身才花 5 秒。这是最经典的存储省钱、计算烧钱陷阱。建议在 ODS/DWD 层用 Snappy 保证速度只在冷数据归档层超过 90 天不查的数据用 Zlib 节约存储。别在 Parquet 上用 LZOParquet 的官方支持压缩算法是 UNCOMPRESSED、SNAPPY、GZIP、LZO、BROTLI、LZ4、ZSTD。但 LZO 在 Parquet 上是兼容但不推荐——很多查询引擎如 DuckDB、Pandas读取 ParquetLZO 会直接报错或回退到全文件读取。如果跨引擎使用请用 Snappy 或 Zstd。ODS→DWD/DWS 换格式需要重建数据如果你的 ODS 层已经存了 6 个月的 Parquet 数据不要直接在 DWD 层切换到 ORC 然后INSERT OVERWRITE——因为 Parquet 和 ORC 的内部 Schema 表达不同Parquet 的INT96时间戳类型在 ORC 里不存在可能有字段精度丢失。建议新建 ORC 表后先抽样对比 100 行的数据一致性确认无精度差异后再全量迁移。没有绝对的赢家只有适合场景的选择你的场景推荐格式理由数据仓库标准数仓星型/雪花模型ORC Zlib压缩比最高谓词过滤强Hive 原生支持大量嵌套数据JSON、ProtobufParquet Snappy嵌套列裁剪天然支持生态广泛Spark/Pandas/DuckDB频繁做列投影查询只读几列Parquet列裁剪性能略好需要和多个计算引擎互通ParquetSpark、Pandas、DuckDB、BigQuery 都原生支持对存储成本敏感ORC Zlib压缩比最高省 30% 存储空间实时 OLAP 查询Presto/TrinoORCTrino 的 ORC Reader 优化极好我个人在数仓建设中的选择是ODS 层用 Parquet来源系统数据可能有嵌套结构DWD/DWS 层用 ORC标准星型模型 谓词下推ADS 层看你用什么 BI 工具接——Tableau 接 Parquet 快Superset 接 ORC 稳。最后强调一点压缩算法比格式更重要。选 Snappy 就保证速度优先选 Zlib/Zstd 就偏存储优先。千万别用 ORC Gzip 组合解压耗时比查询还长也别给 Parquet 上 LZOParquet 对 LZO 的支持不如 ORC。