
# 创建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性能，非必要不启用。
表4Changelog产生机制 
| 类型              | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 适用场景                                                                                    |
|:---|:---|:---|
| 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，设置该参数会引入额外计算，推荐仅在无效变更数据较多情况下使用该参数。                                                                                                                                                                                                                                                                                                                                                                                                                            | 适用于希望解耦数据写入和变更日志生成的高延迟场景，例如每隔一段时间（如小时级）生成一次变更日志。此模式可以减少资源消耗，但会增加变更日志的延迟。                |
   
