Parquet vs CSV:列存到底快在哪 / 该不该把 CSV 改成 Parquet

· 约 4 分钟 📑 Parquet 预览

CSV 简单直接,但只适合小数据 + 临时交换。Parquet 是数据工程的事实标准——pandas / polars / Spark / DuckDB / Iceberg / Delta Lake 全建在它上面。这篇讲清楚 Parquet 凭什么能比 CSV 快 5-100 倍,以及什么时候该选哪个。

行存 vs 列存

CSV 是行存——每行连续存所有字段:

1,Alice,28,Beijing,2026-01-01
2,Bob,35,Shanghai,2026-01-02
3,Carol,42,Guangzhou,2026-01-03

Parquet 是列存——每列连续存所有行:

列 id:        [1, 2, 3]
列 name:      [Alice, Bob, Carol]
列 age:       [28, 35, 42]
列 city:      [Beijing, Shanghai, Guangzhou]
列 date:      [2026-01-01, 2026-01-02, 2026-01-03]

关键差异

  • 查询少数列时:列存只读那几列的字节范围,行存必须读完整行
  • 同列数据连续:相同类型挤一起,压缩率高得多
  • 批量计算:列存对 SIMD 友好,CPU 向量化能用满

列存的四个加成

1. 列裁剪(projection pushdown)

SELECT id, name FROM table 在 Parquet 上只读 id 和 name 两列的字节

50 列的表查 2 列 → 读取量 = 4%,速度直接 25 倍。

CSV 上同样的查询要读完整文件,没有任何优化空间。

2. 谓词下推(predicate pushdown)

每个 row group 在 Parquet 文件里自带 min / max 统计

row group 1: date min=2026-01-01, max=2026-01-31
row group 2: date min=2026-02-01, max=2026-02-28
row group 3: date min=2026-03-01, max=2026-03-31

查询 WHERE date > "2026-02-15" —— row group 1 整段跳过,根本不解码。

10 GB 的 Parquet 可能只解码 1 GB 就给出结果。

3. 字典编码

低基数列(重复值多)—— 比如 country_code 只有 200 个不同值:

原始 1 亿行: ["CN", "CN", "US", "JP", "CN", "US", ...] —— 每行 2 字节 ≈ 200 MB
字典编码:    字典 ["CN", "US", "JP", ...]  +  索引 [0, 0, 1, 2, 0, 1, ...] —— 100 MB 索引 + 几 KB 字典

字典编码后再过一遍压缩(ZSTD / Snappy)—— 最终可能只有 5-10 MB。

4. RLE(Run-Length Encoding)

连续重复值压成 (值, 次数) 对:

原始: [A, A, A, A, B, B, C, C, C]
RLE:  [(A, 4), (B, 2), (C, 3)]

按时间分区的 partition_key、ETL 写入的 batch_id、按地区聚合的 region —— 这些列连续重复几百万行很常见,RLE 后压缩到几字节。

实测体积对比

20 列 1 亿行的电商订单表:

格式大小读取 1 列耗时GROUP BY 耗时
CSV18 GB4 分钟8 分钟
CSV.gz4.5 GB5 分钟9 分钟
Parquet (Snappy)2.1 GB8 秒25 秒
Parquet (ZSTD)1.6 GB9 秒26 秒

Parquet vs CSV.gz——体积小 3 倍、查询快 30-40 倍。压缩 + 列存 + 索引三个组合拳的累积效应。

该选哪个

场景推荐
临时交换给非技术同事CSV
Excel 要打开CSV
< 10 万行CSV(差异不大,方便)
数据仓 / 长期存储Parquet
Spark / DuckDB / 数据湖Parquet(必须)
嵌套结构(list / struct / map)Parquet
严格类型(DECIMAL / TIMESTAMP_NS)Parquet
> 1000 万行Parquet
Iceberg / Delta LakeParquet(这俩底层就是 Parquet)

实用规则:原始落地用 CSV / JSON 方便排查 grep,长期存储和分析仓全转 Parquet。

压缩 codec 选哪个

Codec压缩率速度推荐场景
ZSTD默认首选
Snappy极快延迟敏感(Spark 实时)
GZIP兼容老工具
LZ4极快Hadoop 生态
BrotliWeb 静态资源
未压缩--不推荐

没有特殊理由就 ZSTD level 3——比 GZIP 快 2-3 倍且压缩率更高。pandas to_parquet(compression="zstd") 即可。

row group size 怎么调

默认 128 MB。

场景建议
分布式(Spark / Athena)64-128 MB
单机分析(DuckDB / pandas)32-64 MB(谓词下推更细)
流式追加(Kafka → Parquet)16-32 MB(写入更频繁)
别低于 1 MB元数据开销大于收益

pandas to_parquet(row_group_size=100000) 控制行数,polars pl.write_parquet(row_group_size=100000) 同样。

用 parquet-viewer 验证写入

写完一份 Parquet,最快的验证方式:

  1. 拖进 Parquet 在线预览
  2. 看 schema —— 每列类型对不对(特别是 TIMESTAMP / DECIMAL 这种容易写错的)
  3. 看统计卡 —— 行数 / 行组数 / 压缩比 / codec —— 压缩比低于 2× 找原因
  4. 翻几页 —— 数据值看起来对不对,特别是日期 / 嵌套结构
  5. 全部 OK 才进数据流水线

不用装 pandas / polars / DuckDB,浏览器拖一下就完事。

Parquet 的常见坑

  • TIMESTAMP 时区:写入时不指定 UTC,读出来可能跟预期差 8 小时
  • DECIMAL precision/scale:scale 写错,金额变成几亿倍
  • Map / Set 嵌套:CSV 完全表达不了,Parquet 要用 list of struct
  • 超长字符串:单 cell > 2 GB 的字段会失败(Parquet 物理类型限制)
  • NaN / Null 区分:DOUBLE 列的 NaN 和 NULL 是两回事,写入工具默认不同
  • Iceberg / Delta Lake 不是 Parquet:他们用 Parquet 做存储但有自己的 manifest / log

配套工具

预览看结构 → SQL 跑分析 → 导出给同事,整条链路都在浏览器里。Parquet 不再是必须装 pandas 才能看的”黑盒文件”。

❓ 常见问题

我的数据规模该用 Parquet 还是 CSV?

经验法则——(1) < 10 万行:CSV 即可,体积差不多 + Excel 直接能开 + 全人类可读;(2) 10 万-1000 万行:要分析 / 反复读用 Parquet,单次给非技术同事看用 CSV;(3) > 1000 万行:必须 Parquet,CSV 在这个量级要么慢得不能用要么超出 Excel 行数上限(104 万);(4) 嵌套结构(list / struct / map):必须 Parquet,CSV 表达不了;(5) 类型严格(DECIMAL / TIMESTAMP / UUID):Parquet 自带 schema,CSV 全靠"读时推断"经常出坑。实务规则:原始落地用 CSV / JSON 方便排查,长期存储和分析仓全转 Parquet。

Parquet 真比 CSV 快多少?

典型 5-100 倍,看场景——(1) SELECT col1 FROM table WHERE col2 > 100:Parquet 只读 col1 + col2 两列字节范围,CSV 必须读完整行;如果表有 50 列,Parquet 直接快 25 倍;(2) 谓词下推:Parquet 的 row group 自带 min/max 统计,WHERE date > "2026-01-01" 能跳过整段 row group,10 GB 文件可能只解码 100 MB;(3) 聚合:列连续存储 + SIMD 友好,DuckDB / pandas / polars 在 Parquet 上做 GROUP BY 比 CSV 快 10-50 倍;(4) 解压速度:Snappy 解压速度 ~500 MB/s,ZSTD 1 GB/s,比 CSV 文本解析(~50-200 MB/s)还快。总结——查询型工作负载 Parquet 完胜,全表扫描差异最小。

选 Snappy / GZIP / ZSTD 哪个好?

ZSTD 是当前最优解——压缩率比 GZIP 高 5-15%、比 Snappy 高 30-50%,速度比 GZIP 快 2-3 倍、和 Snappy 相当。没有特殊原因就选 ZSTD level 3-9(默认 3 已经够好)。仍然选 Snappy 的场景——延迟敏感的 Spark 任务(Snappy 解压更快几十微秒)、与老 Hadoop 生态兼容。不要选 GZIP——除非要兼容只支持 GZIP 的老工具,速度和压缩率都不如 ZSTD。LZO 已废弃,不要选。未压缩(NONE)只在 row group 已经字典编码到极致时偶尔有用,普通业务不要选。

字典编码 vs RLE 怎么决定的?

Parquet 写入器自动选——按列基数(cardinality)决定。字典编码适合低基数列(重复值多)——比如 country_code 列只有 200 个不同值,不管行数多少,字典编码后实际存储是 200 个字符串 + 行数个小整数索引。RLE(Run-Length Encoding)适合连续重复——比如按时间分区的 partition_key 列,几百万行连续相同值,RLE 编码后只需要几字节。两者还能组合——RLE 编码的字典索引(RLE_DICTIONARY)。pandas / polars / DuckDB 写 Parquet 时这些选择是自动的,你只需要保证列的语义合理。

为什么我的 Parquet 文件比预期大?

几个常见原因——(1) 没有压缩:写入时忘了指定 codec,pandas 默认是 SNAPPY 但其它工具可能是 NONE;(2) row group size 太小:默认 128 MB,如果设到 1 MB 字典编码效率差,文件会大 2-5 倍;(3) 嵌套深 + 重复结构:每个嵌套层级都需要 definition / repetition level 元数据,深度嵌套的 GeoJSON / Iceberg manifest 元数据可能占文件 30%+;(4) 字符串列高基数:UUID / 自由文本字段无法字典编码,只能压缩;(5) bloom filter 启用:写入时开了 bloom_filter_columns 会增加元数据。用本工具看 schema 卡片的"压缩比"指标,低于 2× 一般有优化空间。

row group 是什么?设多大合适?

Parquet 文件水平切片成 row groups——每个 row group 内部独立存储所有列、独立做压缩 / 字典编码 / 统计 min/max。默认 128 MB(约几十万到几百万行,看列数和压缩率)。为什么要切——(1) 并行读:Spark / DuckDB 把不同 row group 分给不同 worker;(2) 谓词下推:min/max 让 WHERE 条件能跳过整段;(3) 内存控制:解码一个 row group 需要 N MB 内存,太大单机扛不住。调整建议——分布式查询 64-128 MB 最优;单机分析 32-64 MB 略小但谓词下推更细;千万别小于 1 MB(开销大于收益)。

为什么有的工具读不动我的 Parquet?

Parquet 的 spec 演化了 10+ 年——某些新特性老工具不识别。常见兼容性坑——(1) Parquet v2 vs v1 page format:DuckDB / Spark 3.2+ 能读 v2,但老 Hive / 早期 PyArrow 可能只支持 v1;(2) DELTA_BINARY_PACKED 编码:DuckDB / pandas 能读,某些老 Java 客户端不行;(3) 新 logical types(INTERVAL / TIMESTAMP_NS):早期工具不识别;(4) 压缩 codec:ZSTD 是 Parquet 2.4+ 才标准化的,老工具只支持 SNAPPY / GZIP。写入时降级——pandas to_parquet(... use_dictionary=True, compression="snappy", version="1.0") 兼容性最好。本工具用的 hyparquet 跟得比较紧,主流 codec / encoding / logical type 都能读。

📑 打开 Parquet 预览 拖入 .parquet 即看·列 schema/类型/压缩/行数·前 N 行·导出 CSV/JSON·hyparquet 本地解析