语法
MERGE INTO target
USING source
ON source.a = target.a
WHEN MATCHED THEN
UPDATE SET a = source.a, b = source.b, c = source.c
WHEN NOT MATCHED THEN
INSERT (a, b, c) values (a, b, c)
merge into 实际上是一个语法糖, 相对应的语义也可以通过其他的 sql 语法来表达, 例如 UPDATE/DELETE/INSERT, 但是 merge into 的好处是本身一次事务, 因此可以原子性的完成多个修改的操作.
databricks merge into
Paimon merge into
在 Paimon 中, 主键表和非主键表提供了不同的 merge into 实现方式.
非主键表
COW
处理流程
- 通过 join 找出 update/delete 语句所涉及的文件, 及 touched splits. 这一步不需要读取源表的全量字段数据, 只需要读取关联条件所涉及的字段
- 将所有涉及到的数据文件作为数据源读取, 并和 source 表进行 full outer join, 对 join 结果进行 match 和 not match 的处理, 并写出至新文件中
- 将所有的老的数据文件标记为删除文件, 新写入生成的文件标记为新增文件
为什么需要第一次 inner join?
当 Matched Action 中涉及对原始文件的修改需求, 例如 merge action 中存在 UPDATE 或 DELETE 子句时, 就有两种方式
- 对 target 表进行一次 insert overwrite, 那么这样对于涉及 update/delete 的文件自然就完成了更新
- 通过 inner join 找出涉及修改的文件, 再修改完成后通过指定文件 DELETE 的方式标记删除
显然, 在小范围更新的场景下, 第二种方式会更加合适, 可以减少重写文件的数量, 降低存储放大.
为什么需要将 non touched splits 也纳入到计算中?
因为有 WHEN NOT MATCHED
这类 not matched by source/target 时, 就需要将 inner join 未匹配上的数据也参与计算才能使得 WHEN NOT MATCHED
条件判断准确.
劣势:
- 原来的一次 join 操作, 被转化为两次 join
- 如果 source 是一个包含计算逻辑的 view, 也会被展开执行
优势:
- 如果 touched 列表比较小, 可以降低过程中重写的开销以及存储空间
Deletion vector
- 直接基于原始的 target, source 进行 full outer join, 同时读取的时候会读取出 target 表中的 row_index, 用于后续的标记删除
- 基于 merge action 进行处理
- 将 INSERT 和 UPDATE_AFTER 数据写入 add 新增文件
- 将其中的 DELETE 数据标记删除, 构建成 deletion vector 索引文件
优势
- 只有一轮 join 和原始计算开销相近
- 通过标记删除的方式, 同样可以降低存储空间
主键表
主键表的流程和上面的流程其实很像, 因为本身主键表就有更新删除能力, 因此构建出变更行后直接写回主键表即可.
参考
- Delta merge into
- Iceberg merge into