1. 表类型概述
1.1 三种表类型
- 明细表 (Duplicate Key Table):保留所有原始数据记录,允许Key列重复
- 主键表 (Unique Key Table):保证Key列唯一性,相同Key的数据会被覆盖
- 聚合表 (Aggregate Key Table):基于Key列聚合数据,减少存储空间并提升查询性能
1.2 选择原则
- 明细表:任意维度Ad-hoc查询,发挥列存优势
- 主键表:需要唯一主键约束的数据更新场景
- 聚合表:固定模式的报表类查询,通过预聚合提升性能
2. 明细表 (Duplicate Key Table)
2.1 核心特点
- 保留原始数据:存储全量原始数据,避免数据丢失风险
- 不去重不聚合:相同数据也会完整保留
- 灵活查询:基于全量数据做任意维度聚合,支持细粒度分析
2.2 适用场景
- 日志存储:访问日志、错误日志等需要详细记录的审计分析
- 用户行为数据:点击数据、访问轨迹等用户行为分析
- 交易数据:交易行为、订单数据等需要精确对照的记录
2.3 建表示例
CREATE TABLE IF NOT EXISTS example_tbl_duplicate
(log_time DATETIME NOT NULL,log_type INT NOT NULL,error_code INT,error_msg VARCHAR(1024),op_id BIGINT,op_time DATETIME
)
DUPLICATE KEY(log_time, log_type, error_code)
DISTRIBUTED BY HASH(log_type) BUCKETS 10;
2.4 数据操作特性
- 数据以追加方式存储,不进行去重和聚合
- 插入即存储,保留所有数据版本
- 支持任意维度的聚合查询
3. 聚合表 (Aggregate Key Table)
3.1 核心特点
- 预聚合数据:减少重复计算,提升查询性能
- 节省存储空间:只存储聚合后的数据
- 查询加速:通过预聚合减少数据扫描量
3.2 适用场景
- 数据汇总:电商销售业绩、金融风控交易总额、广告点击量等
- 报表分析:驾驶舱报表、用户交易行为分析等
- 不需要原始明细:仅需汇总数据的场景
3.3 工作原理
- 数据导入阶段:按批次导入,生成版本并进行初步聚合
- Compaction阶段:多版本文件合并,减少冗余
- 查询阶段:聚合同一聚合键数据,确保结果准确
3.4 建表示例
CREATE TABLE IF NOT EXISTS example_tbl_agg
(user_id LARGENT NOT NULL,load_dt DATE NOT NULL,city VARCHAR(20),last_visit_dt DATETIME REPLACE DEFAULT "1970-01-01 00:00:00",cost BIGINT SUM DEFAULT "0",max_dwell INT MAX DEFAULT "0"
)
AGGREGATE KEY(user_id, load_dt, city)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;
3.5 聚合方式
聚合方式 | 描述 |
---|
SUM | 多行Value进行累加 |
REPLACE | 新Value替换旧Value |
MAX | 保留最大值 |
MIN | 保留最小值 |
REPLACE_IF_NOT_NULL | 非空值替换 |
HLL_UNION | HyperLogLog算法聚合 |
BITMAP_UNION | 位图并集聚合 |
3.6 AGG_STATE特性
- 实验特性:建议在开发测试环境使用
- 灵活聚合:支持自定义聚合函数签名
- 中间状态存储:存储聚合中间结果而非最终结果
4. 主键表 (Unique Key Table)
4.1 核心特点
- UPSERT操作:基于主键进行更新或插入
- 数据去重:保证Key列唯一性
- 高频更新:支持数据频繁更新场景
4.2 适用场景
- 高频数据更新:OLTP数据库维度表实时同步
- 数据去重:广告投放、客户关系管理基于用户ID去重
- 部分列更新:画像标签、订单状态变更等场景
4.3 实现方式
4.3.1 写时合并 (Merge-on-Write)
- 默认方式:Doris 2.1+版本默认开启
- 立即合并:写入时立即合并相同Key记录
- 性能均衡:兼顾查询和写入性能
- 谓词下推:支持谓词下推到存储层
CREATE TABLE IF NOT EXISTS example_tbl_unique
(user_id LARGETNT NOT NULL,user_name VARCHAR(50) NOT NULL,city VARCHAR(20),age SMALLINT,sex TINYINT
)
UNIQUE KEY(user_id, user_name)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES ("enable_unique_key_merge_on_write" = "true");
4.3.2 读时合并 (Merge-on-Read)
- 历史方式:1.2版本前默认使用
- 增量存储:写入时不合并,保留多版本
- 查询合并:查询或Compaction时进行合并
- 适用场景:写多读少的场景
4.4 更新语义
- 整行更新:默认UPSERT语义,不存在则插入
- 部分列更新:需要写时合并实现,通过参数开启
5. 排序键设计
5.1 排序键作用
- 加速查询:减少数据扫描量,支持范围查询和排序加速
- 优化压缩:有序存储提高压缩效率
- 减少去重成本:Unique Key表更有效去重
5.2 设计建议
- Key列必须在所有Value列之前
- 优先选择整型类型,效率高于字符串
- 整型类型选择遵循"够用即可"原则
- VARCHAR/STRING长度遵循"够用即可"原则
6. 表类型能力对比
特性 | 明细表 | 主键表 | 聚合表 |
---|
Key列唯一约束 | 不支持 | 支持 | 支持 |
同步物化视图 | 支持 | 支持 | 支持 |
异步物化视图 | 支持 | 支持 | 支持 |
UPDATE语句 | 不支持 | 支持 | 不支持 |
DELETE语句 | 部分支持 | 支持 | 不支持 |
导入时整行更新 | 不支持 | 支持 | 不支持 |
导入时部分列更新 | 不支持 | 支持 | 部分支持 |
7. 使用注意事项
7.1 聚合模型的局限性
- count(*)查询性能差:必须扫描所有AGGREGATE KEY列
- 聚合语义限制:MIN/MAX等聚合可能不符合预期
- 解决方案:
- 增加SUM聚合的count列模拟count(*)
- 使用REPLACE聚合类型保证count(*)语义正确
7.2 Unique模型优势
- count(*)性能提升:相比聚合模型有10倍以上提升
- 无需查询时聚合:通过delete bitmap标记删除数据
- 任意列扫描:可选取任意列进行count查询
7.3 Key列意义差异
- 明细表:Key列仅为"排序列"
- 聚合表/主键表:Key列为"排序列"+“唯一标识列”
8. 模型选择建议
8.1 聚合表选择条件
- 固定模式的报表类查询场景
- 需要预聚合降低查询数据量
- 可接受count(*)查询性能损失
8.2 主键表选择条件
- 需要唯一主键约束
- 高频数据更新场景
- 推荐使用写时合并实现
8.3 明细表选择条件
- 任意维度Ad-hoc查询
- 需要保留全量原始数据
- 发挥列存模型优势(只读相关列)
8.4 其他考虑
- 数据模型建表后无法修改,选择需谨慎
- 部分列更新需求需查阅专门文档
- 分区键必须包含在Key列内(Unique表)