快速参考:主键表 vs 非主键表配置速查
快速决策工具:一页纸搞定主键表和非主键表的选择和配置
🎯 30 秒快速决策
是
否
是
否
需要 UPDATE/DELETE?
主键表
数据可能重复?
非主键表
📊 核心差异速查表
| 维度 | 主键表 ✋ | 非主键表 📄 |
|---|---|---|
| 定义 | PRIMARY KEY (id) NOT ENFORCED |
不定义主键 |
| 操作 | INSERT, UPDATE, DELETE | 仅 INSERT |
| Split 策略 | 1 Bucket ≈ 1 Split | 1 Bucket = 多 Splits |
| 批处理并行度 | ≈ Bucket 数(10-100) | >> Bucket 数(100-1000+) |
| 读取方式 | Merge(慢) | Concat(快) |
| 吞吐量 | 150-250 MB/s | 400-600 MB/s |
| 适用场景 | CDC、维度表 | 日志、指标 |
⚡ 配置速查
主键表快速配置
sql
-- 建表
CREATE TABLE my_pk_table (
id BIGINT,
data STRING,
PRIMARY KEY (id) NOT ENFORCED -- 🔑 定义主键
) PARTITIONED BY (dt STRING)
WITH (
'bucket' = '64', -- 🔑 并行度关键:Bucket 数要足够
'compaction.max-file-num' = '50' -- 🔑 频繁 Compaction
);
-- 批处理查询配置
SET 'execution.runtime-mode' = 'batch';
SET 'scan.infer-parallelism' = 'true';
SET 'scan.infer-parallelism.max' = '128'; -- 略大于 Bucket 数
SET 'scan.split-assign-mode' = 'fair'; -- 🔑 推荐 FAIR
SET 'split.target-size' = '128mb'; -- 默认即可
-- 预期:并行度 ≈ 64
非主键表快速配置
sql
-- 建表
CREATE TABLE my_append_table (
log_id STRING,
data STRING
-- 🔑 不定义主键
) PARTITIONED BY (dt STRING)
WITH (
'bucket' = '16', -- 🔑 Bucket 可以少一些
'compaction.max-file-num' = '100'
);
-- 批处理查询配置
SET 'execution.runtime-mode' = 'batch';
SET 'scan.infer-parallelism' = 'true';
SET 'scan.infer-parallelism.max' = '500'; -- 🔑 可以很高
SET 'scan.split-assign-mode' = 'preemptive'; -- 两种都可以
SET 'split.target-size' = '64mb'; -- 🔑 可以更小,增加并行度
-- 预期:并行度 >> 16,取决于数据量
🔢 并行度速算公式
主键表
批处理并行度 ≈ Bucket 数量
流处理并行度 = Bucket 数量(固定)
示例:
- Bucket = 32
- 批处理并行度 ≈ 32
- 流处理并行度 = 32
非主键表
批处理并行度 ≈ 总数据量 / split.target-size
流处理并行度 = Bucket 数量(Fixed Bucket)
流处理并行度 >> Bucket(Unaware Bucket)
示例:
- Bucket = 16
- 每个 Bucket = 500MB
- split.target-size = 128MB
- 批处理并行度 ≈ (16 × 500MB) / 128MB ≈ 63
🎛️ 参数速查
核心参数对比
| 参数 | 主键表推荐 | 非主键表推荐 |
|---|---|---|
bucket |
64-256 | 8-64 |
split.target-size |
128mb-256mb | 64mb-128mb |
scan.infer-parallelism.max |
Bucket × 2 | 500-1000 |
scan.split-assign-mode |
fair | preemptive |
compaction 频率 |
每天 | 每周 |
🏃 性能速查
读取速度对比(100GB 数据)
| 指标 | 主键表 | 非主键表 | 倍数 |
|---|---|---|---|
| 执行时间 | 8 分钟 | 3 分钟 | 2.7x |
| 吞吐量 | 200 MB/s | 550 MB/s | 2.8x |
| CPU 利用率 | 80% | 50% | 0.6x |
| 内存占用 | 16GB | 8GB | 0.5x |
结论 :非主键表读取性能约为主键表的 2.5-3 倍
🛠️ 常见问题速查
Q1: 为什么主键表读取这么慢?
A:
- 并行度低(受 Bucket 数限制)
- 需要 Merge 操作(CPU 密集)
- 多层级文件(IO 开销大)
解决方案:
- 增加 Bucket 数量(提高并行度)
- 定期 Compaction(减少 Merge 开销)
- 考虑改用非主键表(如果不需要更新)
Q2: 我的主键表只有 10 个 Buckets,批处理很慢怎么办?
A:
sql
-- 方案 1:增加 Bucket 数(需要重建表)
ALTER TABLE my_table SET ('bucket' = '64');
-- 方案 2:等待 Compaction 完成(临时方案)
CALL sys.compact('database.my_table');
-- Compaction 后,如果都是高 Level 文件,可以切分
-- 方案 3:改为非主键表(如果不需要更新)
CREATE TABLE my_new_table AS SELECT * FROM my_table;
Q3: 非主键表如何去重?
A:
sql
-- 方案 1:在查询时去重
SELECT DISTINCT * FROM my_append_table WHERE dt = '2026-01-25';
-- 方案 2:在 INSERT 时去重
INSERT INTO target_table
SELECT DISTINCT * FROM source_table;
-- 方案 3:改为主键表(自动去重)
CREATE TABLE my_pk_table (
id BIGINT,
data STRING,
PRIMARY KEY (id) NOT ENFORCED
) AS SELECT * FROM my_append_table;
Q4: 应该选择多少个 Buckets?
A:
主键表:
Bucket 数 = 期望的并行度
推荐:
- 小规模(< 100GB):32-64
- 中规模(100GB - 1TB):64-128
- 大规模(> 1TB):128-256
非主键表:
Bucket 数 = 期望的流式并行度(批处理不受限)
推荐:
- 小规模:8-16
- 中规模:16-32
- 大规模:32-64
原因:非主键表批处理并行度不依赖 Bucket 数
Q5: 如何判断我的表是主键表还是非主键表?
A:
sql
-- 查看表定义
SHOW CREATE TABLE my_table;
-- 输出示例 1(主键表):
-- PRIMARY KEY (id) NOT ENFORCED
--
-- 输出示例 2(非主键表):
-- (没有 PRIMARY KEY)
-- 或者查看元数据
SELECT * FROM my_table$schemas;
📋 配置模板
主键表 - 批处理作业
sql
-- ============ 主键表批处理模板 ============
-- 1. 建表
CREATE TABLE my_pk_table (
id BIGINT,
data STRING,
update_time TIMESTAMP,
PRIMARY KEY (id) NOT ENFORCED
) PARTITIONED BY (dt STRING)
WITH (
'bucket' = '64',
'compaction.max-file-num' = '50',
'compaction.min-file-num' = '10'
);
-- 2. 配置
SET 'execution.runtime-mode' = 'batch';
SET 'scan.infer-parallelism' = 'true';
SET 'scan.infer-parallelism.max' = '128';
SET 'scan.split-assign-mode' = 'fair';
-- 3. 查询
SELECT * FROM my_pk_table WHERE dt = '2026-01-25';
-- 4. 优化建议
-- - 增加 Bucket 数到 128(如果并行度不够)
-- - 定期执行:CALL sys.compact('db.my_pk_table');
非主键表 - 批处理作业
sql
-- ============ 非主键表批处理模板 ============
-- 1. 建表
CREATE TABLE my_append_table (
log_id STRING,
data STRING,
timestamp BIGINT
) PARTITIONED BY (dt STRING)
WITH (
'bucket' = '16',
'compaction.max-file-num' = '100'
);
-- 2. 配置
SET 'execution.runtime-mode' = 'batch';
SET 'scan.infer-parallelism' = 'true';
SET 'scan.infer-parallelism.max' = '500';
SET 'split.target-size' = '64mb';
SET 'scan.split-assign-mode' = 'preemptive';
-- 3. 查询
SELECT * FROM my_append_table WHERE dt = '2026-01-25';
-- 4. 优化建议
-- - 减小 split.target-size 到 32mb(如果需要更高并行度)
-- - 定期合并小文件:CALL sys.compact('db.my_append_table');
主键表 - 流处理作业
sql
-- ============ 主键表流处理模板 ============
-- 1. 建表
CREATE TABLE my_pk_stream_table (
id BIGINT,
data STRING,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'bucket' = '128', -- 流处理并行度 = Bucket 数
'changelog-producer' = 'input',
'scan.mode' = 'latest'
);
-- 2. 配置
SET 'execution.runtime-mode' = 'streaming';
SET 'scan.parallelism' = '128';
SET 'scan.snapshot-time-interval' = '10s';
SET 'execution.checkpointing.interval' = '60s';
SET 'state.backend' = 'rocksdb';
-- 3. 查询
SELECT
COUNT(DISTINCT id) as user_cnt
FROM my_pk_stream_table;
非主键表 - 流处理作业
sql
-- ============ 非主键表流处理模板 ============
-- 1. 建表
CREATE TABLE my_append_stream_table (
log_id STRING,
data STRING
) WITH (
'bucket' = '64', -- 可以比主键表少
'scan.mode' = 'latest'
);
-- 2. 配置
SET 'execution.runtime-mode' = 'streaming';
SET 'scan.parallelism' = '64';
SET 'scan.snapshot-time-interval' = '10s';
SET 'execution.checkpointing.interval' = '60s';
-- 3. 查询
SELECT
COUNT(*) as log_cnt
FROM my_append_stream_table;
🎨 可视化速查
Split 数量对比
场景:1 个分区,10 个 Buckets,每个 1GB
主键表(有 Level 0):
├─ Split 数量:10 个(= Bucket 数)
├─ 并行度:10
└─ 原因:Key Range 重叠,不能切分
非主键表:
├─ Split 数量:80 个(10GB / 128MB)
├─ 并行度:80
└─ 原因:可以自由切分
性能对比
读取 100GB 数据:
主键表(需要 Merge):
[████████░░] 8 分钟
非主键表(顺序拼接):
[███░░░░░░░] 3 分钟
性能提升:2.7x
🚨 常见错误
❌ 错误 1:主键表 Bucket 太少
sql
-- 错误配置
CREATE TABLE my_table (
id BIGINT,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'bucket' = '4' -- ❌ 太少!批处理并行度只有 4
);
-- 正确配置
CREATE TABLE my_table (
id BIGINT,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'bucket' = '64' -- ✅ 并行度可以达到 64
);
❌ 错误 2:非主键表 Bucket 太多
sql
-- 低效配置
CREATE TABLE my_append_table (
log_id STRING
) WITH (
'bucket' = '256' -- ❌ 太多!会产生大量小文件
);
-- 推荐配置
CREATE TABLE my_append_table (
log_id STRING
) WITH (
'bucket' = '16' -- ✅ 适中,批处理并行度通过 Split 切分实现
);
❌ 错误 3:主键表使用小 Split
sql
-- 错误配置
SET 'split.target-size' = '32mb'; -- ❌ 对主键表无效(无法切分)
-- 正确做法
-- 主键表:增加 Bucket 数量而不是减小 Split
ALTER TABLE my_pk_table SET ('bucket' = '128');
💡 优化技巧速查
主键表优化
1. 🔧 提高并行度
└─ 增加 Bucket 数量(核心手段)
2. 🔧 减少 Merge 开销
└─ 定期 Compaction
└─ 减少 Level 0 文件
3. 🔧 批处理优化
└─ 选择 Compaction 完成后的快照
└─ 使用 FAIR 分配模式
4. 🔧 流处理优化
└─ Bucket 数 = 期望并行度
非主键表优化
1. 🔧 提高并行度
└─ 减小 split.target-size(核心手段)
└─ 提高 scan.infer-parallelism.max
2. 🔧 减少小文件
└─ 定期 Compaction
└─ 适当减少 Bucket 数
3. 🔧 批处理优化
└─ 充分利用高并行度
└─ 使用 PREEMPTIVE 分配模式
4. 🔧 流处理优化
└─ 考虑使用 Unaware Bucket(如果允许)
📞 决策热线
我应该用哪种表?
问自己 3 个问题:
1. 是否需要 UPDATE 或 DELETE?
└─ 是 → 主键表
└─ 否 → 继续
2. 数据是否可能重复(需要去重)?
└─ 是 → 主键表
└─ 否 → 继续
3. 读取性能是否非常关键?
└─ 是 → 非主键表
└─ 否 → 都可以,推荐非主键表(性能更好)
我的配置合理吗?
检查清单:
主键表:
☐ Bucket 数 >= 32?
☐ scan.split-assign-mode = 'fair'?
☐ 定期执行 Compaction?
☐ scan.infer-parallelism.max >= Bucket × 2?
非主键表:
☐ split.target-size <= 128mb?
☐ scan.infer-parallelism.max >= 200?
☐ Bucket 数不要太多(<= 64)?
☐ 定期合并小文件?
🔗 相关文档链接
- 详细对比:《Paimon 主键表 vs 非主键表核心差异》
- Split 机制:《Paimon Split 机制深度解析》
- 配置指南:《Paimon Flink 配置实战指南》
- 读取流程:《Paimon 分布式读取数据完整流程》
如果你喜欢这篇文章,请转发、点赞。扫描下方二维码关注我们,您会收到更多优质文章推送
在这里插入图片描述

关注「Java源码进阶」,获取海量java,大数据,机器学习资料!