更新时间:2026-06-11 GMT+08:00
分享

创建Paimon主键表

操作场景

创建表时指定主键(Primary Key),则创建的表为主键表;主键由列组成,且主键值须唯一。主键表支持insert、update、delete操作。

在存储桶内Paimon数据按主键排序后保存,过滤查询主键时查询性能高。主键表使用LSM树结构存储数据,支持高效读写。

语法结构

例如,创建一张分区键为pt,主键为pt, id,分桶为4的主键表:

-- SPARK SQL
CREATE TABLE my_table (
    id  INT,
    a  STRING,
    pt STRING
) TBLPROPERTIES (
    'primary-key' = 'id',
    'bucket' = '4'
);

Paimon主键表中每行数据的主键值各不相同,如果将多条具有相同主键的数据写入Paimon主键表,将根据数据合并机制对数据进行合并,相关参数介绍请参见表3

分桶

分桶(Bucket)是Paimon表读写操作的最小单元。非分区表的所有数据以及分区表每个分区的数据,都会被进一步划分到不同的分桶中,以便多个并发同时进行读写,提高数据读写性能。用户可以通过bucket-key指定分桶列,如果未指定则使用主键作为Bucket Key。支持表1中的分桶类型。

表1 分桶类型

类型

分桶定义

说明

动态分桶(默认)

创建Paimon主键表时,不指定Bucket或指定'bucket' = '-1'

  • 动态分桶的Paimon表不支持多个作业并发写入。
  • 动态分桶的Paimon表支持跨分区更新主键,详细操作请参见表2
  • 动态分桶的Paimon表会先将数据写入已有的分桶中,当分桶的数据量超过限制时,再自动创建新的分桶。
    • dynamic-bucket.target-row-num:每个分桶最多存储的数据条数。默认值为2000000。
    • dynamic-bucket.initial-buckets:初始的分桶数。如果不设置,初始将会创建等同于writer算子并发数的分桶。

固定分桶

创建Paimon主键表时,在参数中指定'bucket' = '<num>',即可指定非分区表的分桶数为<num>,或者分区表单个分区的分桶数为<num><num>是一个大于0的整数。

  • 固定分桶的主键表,分区表的主键必须包含全部分区键。
  • 固定分桶的Paimon表,默认使用每条数据Primary Key的hash值确定数据属于的分桶;使用bucket-key指定分桶列时,分桶列必须是Primary Key子集。
  • 分桶数过大过小都可能导致读写性能降低,建议每个分桶的数据大小在2GB左右(2GB是指数据内容大小),目的是将桶内Parquet文件大小控制在200MB左右。2GB是一个经验值,因不同业务数据分布特征不同,请按实际情况调整分桶的数据大小。

动态分桶表更新

表2 动态分桶表更新

类型

说明

跨分区更新的动态分桶表(主键不完全包含部分分区键)

Paimon无法根据主键确定该数据属于的分区及分桶,需要内存中维护主键与分区以及分桶编号的映射关系。相比固定分桶而言,数据量较大的表可能会产生明显的性能损失。

数据合并机制会对跨分区更新的结果产生如下不同影响:

  • deduplicate:数据将会从老分区删除,并插入新分区。
  • aggregation与partial-update:数据将会直接在老分区中更新,无视新数据的分区键。
  • first-row:如果相同主键的数据已经存在,则新数据将被直接丢弃。

非跨分区更新的动态分桶表(主键包含全部分区键)

Paimon可以确定该主键属于的分区,但无法确定属于的分桶,需要使用额外的堆内存创建索引,以维护主键与分桶编号的映射关系。

数据合并机制

Paimon提供多种合并引擎,支持不同的合并机制,适用于不同的场景。默认情况下,Paimon会按照数据的输入顺序确定数据合并顺序,最后写入的数据会被认为是最新数据。如果输入数据流存在乱序,可以通过在表属性中设置'sequence.field' = '<column-name>',具有相同主键的数据会按照<column-name>列的值从小到大进行合并。可以设置Sequence Field属性的数据类型有:TINYINT、SMALLINT、INTEGER、BIGINT、TIMESTAMP和TIMESTAMP_LTZ。

表3 数据合并机制

类型

说明

适用场景

去重(Deduplicate)

默认合并引擎,仅保留相同主键最新记录。如果最新记录是DELETE记录,则所有具有相同主键的记录都将被删除。可以通过配置ignore-delete=true来忽略删除操作。

适用于需要确保主键唯一性,并且只关心最新数据的场景。

部分更新(Partial Update)

允许通过多次更新逐步完善记录的各个列,即根据相同主键,逐个更新字段的最新值,但不会覆盖非空值。默认情况下,部分更新引擎不接受删除记录,可以通过配置ignore-delete来忽略删除记录,或配置sequence-group来撤销部分列。

适用于需要逐步更新记录的部分字段,而不影响其他字段的场景。适合主键多流Join的场景。

预聚合(Aggregation)

根据指定的聚合函数,对具有相同主键的记录进行字段级别的聚合。每个非主键字段可以指定聚合函数,未指定的字段默认使用last_non_null_value聚合。

适用于需要对数据进行聚合计算的场景,例如求和、计数等。

首行(First Row)

保留具有相同主键的第一条记录,与去重引擎不同,首行引擎保留最早的记录。

适用于需要保留首次出现的记录,而忽略后续更新的场景。

Changelog产生机制

流式写入时,通过指定changelog-producer表属性,可选择从表文件生成变更日志的方式。启用changelog producer会显著降低compaction性能,非必要不启用。

表4 Changelog产生机制

类型

说明

适用场景

none(默认)

默认模式,不生成额外的变更日志。Paimon源只能看到跨快照合并后的更改,无法形成完整的变更日志。

适用于不需要完整变更日志的消费者,例如数据库系统。

input

直接使用输入记录作为完整变更日志源,保存到独立的changelog文件中,必须输入完整的变更日志。

适用于输入本身就是完整的变更日志的情况,例如来自数据库的 CDC(变更数据捕获)数据,或由Flink有状态计算生成的数据。

lookup

在提交数据写入之前,通过lookup生成变更日志。Paimon会在内存和本地磁盘上缓存数据,以生成完整的变更日志。

可以设置changelog-producer.row-deduplicate=true避免对相同记录生成-U/+U,设置该参数会引入额外计算,推荐仅在无效变更数据较多情况下使用该参数。

lookup在内存和本地磁盘缓存数据,通过以下参数进行性能调优:

  • lookup.cache-file-retention:缓存文件保留时间(默认值为1小时)。
  • lookup.cache-max-disk-size:本地磁盘缓存上限(默认无限制)。
  • lookup.cache-max-memory-size:内存缓存上限(默认值为256 MB)。

适用于输入无法生成完整变更日志,但希望避免昂贵的 “Normalize” 操作的情况。需要注意的是,Lookup操作会消耗一定的资源,比较适用于实时性要求较高场景(分钟级)。

full-compaction

通过比较完全压缩之间的结果,生成差异作为变更日志。变更日志的延迟受到完全压缩频率的影响,可以通过full-compaction.delta-commits(Flink)、compaction.optimization-interval(Spark)参数控制。

可以设置changelog-producer.row-deduplicate=true避免对相同记录生成 -U/+U,设置该参数会引入额外计算,推荐仅在无效变更数据较多情况下使用该参数。

适用于希望解耦数据写入和变更日志生成的高延迟场景,例如每隔一段时间(如小时级)生成一次变更日志。此模式可以减少资源消耗,但会增加变更日志的延迟。

相关文档