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 耗时 |
|---|---|---|---|
| CSV | 18 GB | 4 分钟 | 8 分钟 |
| CSV.gz | 4.5 GB | 5 分钟 | 9 分钟 |
| Parquet (Snappy) | 2.1 GB | 8 秒 | 25 秒 |
| Parquet (ZSTD) | 1.6 GB | 9 秒 | 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 Lake | Parquet(这俩底层就是 Parquet) |
实用规则:原始落地用 CSV / JSON 方便排查 grep,长期存储和分析仓全转 Parquet。
压缩 codec 选哪个
| Codec | 压缩率 | 速度 | 推荐场景 |
|---|---|---|---|
| ZSTD | 优 | 快 | 默认首选 |
| Snappy | 中 | 极快 | 延迟敏感(Spark 实时) |
| GZIP | 良 | 慢 | 兼容老工具 |
| LZ4 | 中 | 极快 | Hadoop 生态 |
| Brotli | 优 | 中 | Web 静态资源 |
| 未压缩 | - | - | 不推荐 |
没有特殊理由就 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,最快的验证方式:
- 拖进 Parquet 在线预览
- 看 schema —— 每列类型对不对(特别是 TIMESTAMP / DECIMAL 这种容易写错的)
- 看统计卡 —— 行数 / 行组数 / 压缩比 / codec —— 压缩比低于 2× 找原因
- 翻几页 —— 数据值看起来对不对,特别是日期 / 嵌套结构
- 全部 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
配套工具
- DuckDB SQL 工作台 —— 想跑 SQL / JOIN / 聚合,从预览升级到分析
- JSON 工具 —— 嵌套字段导出 JSON 后做 JSONPath
- 代码格式化 —— 美化 SQL / JSON
预览看结构 → SQL 跑分析 → 导出给同事,整条链路都在浏览器里。Parquet 不再是必须装 pandas 才能看的”黑盒文件”。