三大数据湖表格式(Iceberg、Hudi、Delta Lake)全面解析与选型指南

发布于 2026-06-11

目录

写在前面:我们到底在比较什么

讨论”三大数据湖”时,一个极其常见的误解是把它们当成”数据湖产品”。它们不是数据湖,而是”数据湖表格式(Table Format)”。 这个区分至关重要,是理解后面一切的前提。

换句话说,Iceberg / Hudi / Delta Lake 解决的是同一个核心问题:

对象存储上只有”文件”,没有”表”。怎么在不可变的文件之上,模拟出数据库才有的 ACID 事务、行级更新、Schema 演化和历史版本?

背景:为什么需要表格式

传统 Hive 表的困境

在 Iceberg/Hudi/Delta 出现之前,大数据世界用的是 Hive 表。Hive 的做法是:用一个目录代表一张表,用子目录代表分区,目录里放 Parquet 文件,元数据(有哪些分区)记在 Hive Metastore 里。

这套方案跑了很多年,但有几个致命缺陷:

1. 没有事务(ACID)。 多个作业同时写一张表,可能互相覆盖、产生脏数据。一个写作业跑到一半失败,会留下半截文件,下游读到不完整的数据。没有”提交”这个概念,写入不是原子的。

2. 改数据极其痛苦。 Parquet 文件是不可变的。要更新一行、删除一行(比如 GDPR 合规删除用户数据),唯一办法是把整个分区读出来、改完、整个覆盖写回。一个分区几十 GB,改一行要重写几十 GB。

3. 分区裁剪靠”猜目录”。 Hive 靠目录路径做分区裁剪(WHERE dt='2024-01-01' → 只扫 dt=2024-01-01/ 目录)。但这要求查询条件必须命中分区字段,而且 LIST 大量目录在对象存储上极慢(S3 的 list 操作又慢又贵)。

4. Schema 变更不可靠。 加字段、改字段类型、重命名列,Hive 处理得很脆弱,经常导致老数据读不出来或读错。

表格式的使命

三大表格式本质上都是为了根治上述四个问题而生。它们的共同思路是:

不再用”目录结构”来定义一张表,而是用一套独立的、版本化的”元数据文件”来精确记录:这张表此刻由哪些数据文件组成、每个文件的统计信息是什么、Schema 是什么。

数据文件依然是不可变的 Parquet/ORC,但”表 = 哪些文件的集合”这件事,被一层智能的元数据管起来了。每次写入产生一个新版本的元数据,旧版本保留——这就同时实现了原子提交(切换元数据指针)和时间旅行(读旧版本元数据)。

Apache Iceberg:以”快照”为核心的表格式

Iceberg 由 Netflix 发起,为解决其在 S3 上 PB 级数据的管理难题而生,后捐给 Apache。它的设计哲学是”开放、引擎中立、面向超大规模分析表”——不绑定任何计算引擎,不依赖 Hive Metastore 的目录假设。

核心结构:三层元数据树

Iceberg 的精髓在于它的元数据组织方式。一张 Iceberg 表的元数据是一棵树,从上到下三层:

metadata.json (表的当前状态:schema, 分区定义, 快照列表)


manifest list (清单列表:本快照由哪些 manifest 组成)


manifest file (清单文件:记录一批数据文件及其统计信息)


data files (真正的 Parquet/ORC 数据文件)

alt text

快照(Snapshot)与原子提交

每一次写操作,Iceberg 都会生成一个新的快照——一个完整记录”此刻表由哪些文件组成”的版本。提交的本质是:原子地把 metadata.json 里的”当前快照指针”从旧快照切换到新快照(通过 catalog 的原子操作保证,比如一次 CAS)。

这带来三个直接好处:

隐藏分区(Hidden Partitioning)

这是 Iceberg 最受称道的设计。Hive 时代,分区字段是物理目录,用户查询必须显式写对分区条件才能裁剪。Iceberg 把分区变成了一个”从某列计算出来的逻辑转换”,记录在元数据里,对用户透明。

举例:你定义”按 event_time 的天分区”(days(event_time))。用户查询时只需写 WHERE event_time > '2024-01-01',Iceberg 自动推导出该裁剪哪些分区——用户根本不需要知道分区是怎么切的。而且分区方式可以演化(从按天改成按小时),老数据不用重写。

元数据级的文件裁剪

因为 manifest file 里记录了每个数据文件的列级 min/max 统计,Iceberg 在规划查询时,不需要 list 目录、也不需要打开数据文件,光读元数据就能判断”这个文件的 age 列范围是 20~30,而我查 age>50,那这个文件直接跳过”。

在对象存储上,这是巨大的性能优势——避免了 Hive 那种昂贵的 LIST 操作,把”找文件”从 O(目录遍历) 变成了 O(读元数据)。

行级更新:MOR 与 COW

Iceberg 支持两种更新模式(v2 格式):

Iceberg 的特点总结

Apache Hudi:以”流式 upsert”为核心的表格式

Hudi(Hadoop Upserts Deletes and Incrementals)由 Uber 发起。Uber 的核心诉求非常明确:把数据库的变更(CDC)近实时地、增量地灌入数据湖,并支持高效的 upsert(更新插入)。所以 Hudi 从第一天起就是为”流式增量写入 + 行级更新”而生的,这与 Iceberg”面向大规模分析查询”的出发点不同。

核心概念:主键 + 索引

Hudi 与 Iceberg/Delta 最本质的区别在于:Hudi 表有”主键(record key)“的概念,并维护一套”索引”。

为什么需要索引?因为 upsert 的核心难题是:当一条新记录进来,要判断它是”插入新行”还是”更新已有行”,就必须快速定位”这个主键之前写在哪个文件里”。Hudi 用索引(Bloom Filter 索引、HBase 索引、Bucket 索引等)来做这个”主键 → 文件”的映射,这是它能高效 upsert 的根基。

Iceberg/Delta 早期没有这种内建的主键索引机制,所以它们的 upsert 更多是”merge into”式的全表/分区扫描合并,在高频 upsert 场景下不如 Hudi。

两种表类型:COW 与 MOR

Hudi 把更新策略做成了表级别的两种类型(这是 Hudi 的标志性设计):

Copy on Write (COW) 表

Merge on Read (MOR) 表

关键机制:Timeline(时间线)

Hudi 用一个叫 Timeline 的核心抽象来管理表上发生的所有动作。Timeline 是一串有序的”瞬间(instant)“,每个 instant 记录一个动作(commit、compaction、clean、rollback 等)及其状态(requested、inflight、completed)。

Timeline 是 Hudi 实现 ACID、增量查询、回滚的基础。它本质上类似数据库的事务日志,但记录的是表级别的操作历史。

杀手锏:增量查询(Incremental Query)

这是 Hudi 区别于其他两者的强大能力。基于 Timeline,Hudi 可以让你查询”从某个 commit 时间点之后,所有发生变更的记录”——而不是查询全表的当前状态。

-- 只拉取 commit 时间 > 某时刻的增量变更
SELECT * FROM hudi_table
WHERE _hoodie_commit_time > '20240101120000'

这让 Hudi 天然适合构建增量 ETL 管道:下游不用每次全量扫描,只处理上游新变化的部分,配合 InLong/Flink CDC 把数据库变更入湖后,下游层层增量加工。

Hudi 的特点总结

Delta Lake:以”事务日志”为核心的表格式

Delta Lake 由 Databricks(Spark 的母公司)开源。它的设计动机是:在 Spark 生态里,给数据湖加上可靠的 ACID 事务,让数据湖能像数仓一样可靠地支撑 BI 和 ETL。它与 Spark/Databricks 平台的整合是最丝滑的。

核心结构:事务日志 _delta_log

Delta 的全部魔法都在一个叫 _delta_log 的目录里。这个目录里是一串 JSON 文件(事务日志),每个 JSON 记录一次”提交(commit)“做了什么——增加了哪些文件、删除了哪些文件、Schema 变成了什么。

my_table/
├── _delta_log/
│   ├── 00000000000000000000.json   (commit 0)
│   ├── 00000000000000000001.json   (commit 1)
│   ├── 00000000000000000002.json   (commit 2)
│   └── 00000000000000000010.checkpoint.parquet  (检查点,加速)
└── (一堆 Parquet 数据文件)

关键机制:基于日志回放的状态计算

理解 Delta 的关键是:表的当前状态 = 从某个 checkpoint 开始,依次回放后续所有 commit JSON 得到的结果。

这种”事务日志 + 回放”的模型,和数据库的 WAL(预写日志)思想一脉相承,对熟悉数据库的人非常友好。原子提交靠”原子地创建下一个编号的 JSON 文件”实现(同名文件只能被一个写者成功创建,从而解决并发冲突)。

功能特性

Delta Lake 的特点总结

三者横向对比

核心维度对比表

维度IcebergHudiDelta Lake
发起方NetflixUberDatabricks
设计初衷超大规模分析表、引擎中立流式 upsert / CDC 入湖Spark 生态的 ACID 数据湖
元数据核心快照 + 三层 manifest 树Timeline + 索引事务日志 _delta_log
原子提交方式切换元数据指针(catalog 原子操作)Timeline instant 状态流转原子创建递增编号的 JSON
行级更新COW / MOR(v2)COW / MOR(表类型)COW 为主(渐进 deletion vectors)
主键 & 索引无内建主键索引有,核心能力无内建主键索引
增量查询支持(incremental read)最强,独门绝技支持(CDF, Change Data Feed)
隐藏分区支持,独门优势不支持(需显式)不支持(用 generated column 近似)
Schema 演化强(含分区演化)支持强(含 enforcement)
时间旅行支持支持支持
引擎中立性最强中(Spark 为主,扩展中)中(Spark 为主,扩展中)
流式写入成熟度良好最成熟良好(流批一体)

三种元数据哲学的本质区别

如果只记一件事,请记住三者元数据模型的根本差异——这决定了它们各自的性格:

选型指南

按场景选择

选 Iceberg,如果:

选 Hudi,如果:

选 Delta Lake,如果:

一个务实的判断顺序

  1. 先看生态绑定:已经深度用 Databricks → Delta;要多引擎开放 → Iceberg。
  2. 再看核心负载:主场景是高频 CDC upsert + 增量消费 → Hudi。
  3. 趋势参考:近两年 Iceberg 的社区势头和厂商支持(含 Snowflake、Databricks 通过 UniForm 兼容、各云厂商)增长最猛,正在成为”开放湖仓”的事实标准;但在纯流式 upsert 场景,Hudi 依然有成熟度优势。

一个常被忽视的现实

三者正在互相趋同:Iceberg 在补强 upsert,Hudi 在扩展多引擎,Delta 在增强开放性(UniForm 甚至能让一张表同时被当作 Delta 和 Iceberg 读)。所以选型不必焦虑”赌错技术”——把数据存成开放的 Parquet、用标准 SQL 操作,迁移成本是可控的。真正决定体验的,往往是你的计算引擎和云平台生态,而不是表格式本身的功能清单。