表格式详解:V1 vs V2 格式,何时选择 V2?
好,咱们进入第三章。说实话,V1 和 V2 的格式选择,是我在咨询中被问得最多的问题之一。很多人觉得「V2 肯定比 V1 好,无脑上 V2 就对了」。嗯,事情没那么简单。
我见过不少团队,为了追新把 V1 表强行升级到 V2,结果发现自己的场景根本用不上 V2 的特性,反而多了一堆维护成本。今天我就把这两个版本的底裤扒干净,告诉你什么时候该选 V2,什么时候老老实实用 V1 反而更香。
V1 和 V2 到底差在哪?
先看一张总览图,把两个版本的核心差异刻在脑子里。
V1 格式:简单可靠的老伙计
V1 是 Iceberg 的第一个稳定格式。它的设计哲学很纯粹——只做追加写。你想想看,在数据湖场景里,大部分数据都是日志、事件、点击流这种只增不减的东西。V1 就是为这个场景量身定做的。
V1 的删除机制很原始。要删一行数据?它不会真的去改 Parquet 文件,而是在 Manifest 里标记这个文件「部分失效」。读的时候跳过这些文件。说白了,就是「假装删掉了」。
V1 的核心优势:
- 写入路径极简,没有 Delete File 的合并开销
- 读取性能稳定,不需要在运行时合并 Delete File
- 兼容性最好,几乎所有 Iceberg 引擎都完美支持
- 元数据体积小,没有额外的删除记录文件
我在一个日增 10TB 的日志平台项目里,用的就是 V1。为什么?因为数据只追加,从不修改。V1 的写入吞吐能跑到单节点 200MB/s,V2 在同样硬件下只能跑到 150MB/s 左右。这 25% 的性能差距,在规模大了以后就是真金白银。
V2 格式:为变更数据捕获而生
V2 最大的改进,就是引入了 Delete File 的概念。它有两种类型:
| Delete File 类型 | 工作原理 | 适用场景 | 性能特点 |
|---|---|---|---|
| Position Delete | 记录 (文件路径, 行号) 对,精确标记要删除的行 | 小批量精确删除,CDC 场景 | 读取时需合并,有额外 IO |
| Equality Delete | 记录被删除行的主键或等值条件 | 基于主键的 UPSERT,去重 | 需要哈希匹配,CPU 开销较大 |
有了 Delete File,V2 就能支持真正的 MERGE INTO 和 DELETE FROM 了。你想想看,在实时数仓里,你要做缓慢变化维(SCD),或者从 Kafka 消费 CDC 数据做回放——没有行级更新根本玩不转。
我的经验:如果你用 Flink CDC 同步 MySQL 到 Iceberg,必须用 V2。Flink 的 Changelog 流会产生大量的 UPDATE_BEFORE 和 DELETE 消息,V1 根本处理不了。我曾经见过一个团队用 V1 接 CDC,结果数据重复率高达 30%,排查了三天才发现是格式问题。
什么时候选 V2?一张决策表搞定
别纠结了,直接看这张表。我根据实际项目踩过的坑,给你列了 6 个典型场景:
| 场景 | 推荐格式 | 理由 |
|---|---|---|
| 日志/事件流(只追加) | V1 | 写入性能高 20-30%,无 Delete File 开销 |
| CDC 实时同步(MySQL → Iceberg) | V2 | 必须支持行级 DELETE/UPDATE |
| 维度表(缓慢变化维) | V2 | 需要 MERGE INTO 做 SCD Type 2 |
| 批量 ETL(全量覆盖) | V1 | INSERT OVERWRITE 在 V1 下更高效 |
| 数据湖上的 OLAP 查询 | V1 | 读取时无需合并 Delete File,查询更快 |
| 湖仓一体(需要 ACID 事务) | V2 | V2 的快照引用完整性更强 |
⚠️ 避坑指南:我曾经帮一个客户做格式选型,他们的场景是「每天全量覆盖一次,但偶尔需要修正几条历史数据」。他们选了 V2,结果每次修正都会产生 Delete File,三个月后 Delete File 的数量超过了数据文件本身,查询性能下降了 60%。
正确的做法是:主体用 V1,修正操作通过分区覆盖 + 重跑 ETL 来实现。如果修正频率极低(比如一个月一次),完全没必要上 V2。
V2 的隐藏成本:Delete File 合并
很多人只看到 V2 的功能强大,没看到它的代价。V2 的 Delete File 不会自动消失,它们会不断累积。你每次做 UPDATE,都会生成新的 Delete File。读数据的时候,Iceberg 需要把数据文件和 Delete File 做合并。
这个合并操作有多贵?我拿一个真实案例给你看:
-- 假设有一张 1TB 的表,每天有 100 万行更新
-- 30 天后,Delete File 的总大小可能达到 50GB
-- 每次全表扫描,需要额外读取 50GB 的 Delete File
-- 并且要在内存中做行级合并
-- 解决办法:定期执行压缩(Compaction)
CALL catalog.system.rewrite_data_files(
table = 'db.orders',
options = map(
'rewrite-all', 'true',
'delete-file-threshold', '10' -- 当 Delete File 超过 10 个时触发
)
);
嗯,这里要注意。压缩操作本身也很贵,它会把数据文件和 Delete File 合并成新的数据文件。我建议你在业务低峰期做,比如凌晨 3 点。而且不要每天都做,根据你的更新量来定——我一般建议 Delete File 数量超过数据文件数量的 20% 时再触发。
格式升级:从 V1 到 V2 的迁移路径
如果你已经在用 V1,想升级到 V2,可以。但别直接 ALTER TABLE,那是个坑。
正确的做法是:
- 先评估必要性——你真的需要行级更新吗?
- 创建新表——用 V2 格式建一张新表
- 迁移数据——用 INSERT INTO 把 V1 表的数据全量写入 V2 表
- 切换应用——改数据源指向,旧表保留一段时间做回滚
为什么不建议原地升级?因为 V1 表的 Manifest 结构和 V2 不兼容。原地升级会导致历史快照全部失效,一旦回滚就完蛋了。我见过有人直接在生产环境跑 ALTER TABLE ... SET FORMAT_VERSION 2,结果查询直接报错,回滚都回不去——因为 V2 的写入器不认 V1 的 Manifest 格式。
小结:选型三问
最后,给你一个简单的自检清单。选格式之前,问自己三个问题:
- 我的数据会修改吗?——如果只追加,V1 更香
- 修改频率有多高?——每天超过 1% 的行变更,考虑 V2;否则 V1 + 定期重刷
- 我的查询对延迟敏感吗?——如果要求秒级响应,V1 的读取性能更稳定
说白了,V2 是个好技术,但它不是银弹。选对格式,比用对格式更重要。我在项目里见过太多「为了用 V2 而用 V2」的案例,最后都付出了性能代价。记住一句话:工具服务于场景,不是场景服务于工具。