Doris数据模型
数据模型
- 在Doris中,数据以表(Table)的形式进行逻辑上的描述。一张表包括行(Row)和列(Column)。Row即用户的一行数据,Column用于描述一行数据中不同的字段。
- Column可以分为两大类:Key和Value。从业务角度看,Key和 Value可以分别对应维度列和指标列。Doris的Key列是建表语句中指定的列,建表语句中的关键字unique key或aggregate key或 duplicate key后面的列就是Key列,除了Key列剩下的就是Value 列。
一、明细模型(Duplicate Key Model)
1、适用场景:数据既没有主键,也没有聚合需求。
2、数据按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。
3、在建表语句中指定的Duplicate Key,只是用来指明数据存储按照哪些列进行排序。在Duplicate Key的选择上,建议选择前2-4列即可。
4、实例
(1)一个表有如下的数据列,没有主键更新和基于聚合键的聚合需求。
ColumnName | Type | Comment |
---|---|---|
timestamp | DATETIME | 日志时间 |
type | INT | 日志类型 |
error_code | INT | 错误码 |
error_msg | VARCHAR(128) | 错误详细信息 |
op_id | BIGINT | 负责人 ID |
op_time | DATETIME | 处理时间 |
(2)当创建表的时候没有指定Unique、Aggregate或Duplicate 时,会默认创建一个Duplicate模型的表,并自动按照一定规则选定排序列。建表语句举例如下,没有指定任何数据模型,则建立的是明细模型(Duplicate),排序列系统自动选定了前3列。
CREATE TABLE IF NOT EXISTS example_tbl_by_default
(
`timestamp` DATETIME NOT NULL COMMENT "日志时间",
`type` INT NOT NULL COMMENT "日志类型",
`error_code` INT COMMENT "错误码",
`error_msg` VARCHAR(1024) COMMENT "错误详细信息",
`op_id` BIGINT COMMENT "负责人id",
`op_time` DATETIME COMMENT "处理时间"
)
DISTRIBUTED BY HASH(`type`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
MySQL > desc example_tbl_by_default;
+------------+---------------+------+-------+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-------+---------+-------+
| timestamp | DATETIME | No | true | NULL | NONE |
| type | INT | No | true | NULL | NONE |
| error_code | INT | Yes | true | NULL | NONE |
| error_msg | VARCHAR(1024) | Yes | false | NULL | NONE |
| op_id | BIGINT | Yes | false | NULL | NONE |
| op_time | DATETIME | Yes | false | NULL | NONE |
+------------+---------------+------+-------+---------+-------+
6 rows in set (0.01 sec)
(3)无排序列的默认明细模型:在表属性中增加如下配置(在PROPERTIES()中添加)
"enable_duplicate_without_keys_by_default" = "true"
(4)指定排序列的明细模型:在建表语句中指定Duplicate Key,用来指明数据存储按照这些Key列进行排序
二、主键模型(Unique Key Model)
1、适用场景:用户有数据更新需求。
2、主键模型能够保证Key(主键)的唯一性,当用户更新一条数据时,新写入的数据会覆盖具有相同key(主键)的旧数据。两种实现方式:
(1)读时合并。用户在进行数据写入时不会触发任何数据去重相关的操作,所有数据去重的操作都在查询或者compaction时进行。读时合并的写入性能较好,查询性能较差,同时内存消耗也较高。
(2)写时合并。在数据写入阶段完成所有数据去重的工作,因此能够提供非常好的查询性能。Unique模型的默认实现。
3、数据更新的语意
(1)Unique模型默认的更新语意为整行UPSERT,即UPDATE OR INSERT,该行数据的key如果存在,则进行更新,如果不存在,则进行新数据插入。
(2)部分列更新。如果用户希望更新部分字段,需要使用写时合并实现,并通过特定的参数来开启部分列更新的支持。
4、实例:用户基础信息表,主键为user_id + username
ColumnName | Type | IsKey | Comment |
---|---|---|---|
user_id | BIGINT | Yes | 用户id |
username | VARCHAR(50) | Yes | 用户昵称 |
city | VARCHAR(20) | No | 用户所在城市 |
age | SMALLINT | No | 用户年龄 |
sex | TINYINT | No | 用户性别 |
phone | LARGEINT | No | 用户电话 |
address | VARCHAR(500) | No | 用户住址 |
register_time | DATETIME | No | 用户注册时间 |
(1)读时合并的建表语句
CREATE TABLE IF NOT EXISTS example_tbl_unique
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`phone` LARGEINT COMMENT "用户电话",
`address` VARCHAR(500) COMMENT "用户地址",
`register_time` DATETIME COMMENT "用户注册时间"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
(2)写时合并的建表语句
CREATE TABLE IF NOT EXISTS example_tbl_unique_merge_on_write
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`phone` LARGEINT COMMENT "用户电话",
`address` VARCHAR(500) COMMENT "用户地址",
`register_time` DATETIME COMMENT "用户注册时间"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
"enable_unique_key_merge_on_write" = "true"
);
三、聚合模型(Aggregate Key Model)
1、假设业务有如下数据表模式
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | LARGEINT | 用户id | |
date | DATE | 数据灌入日期 | |
city | VARCHAR(20) | 用户所在城市 | |
age | SMALLINT | 用户年龄 | |
sex | TINYINT | 用户性别 | |
last_visit_date | DATETIME | REPLACE | 用户最后一次访问时间 |
cost | BIGINT | SUM | 用户总消费 |
max_dwell_time | INT | MAX | 用户最大停留时间 |
min_dwell_time | INT | MIN | 用户最小停留时间 |
2、建表语句(省略建表语句中的Partition和Distribution信息)
CREATE TABLE IF NOT EXISTS example_tbl_agg1
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`date` DATE NOT NULL COMMENT "数据灌入日期时间",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
`cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间",
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
(1)表中的列按照是否设置了AggregationType,分为Key (维度列) 和Value(指标列)。没有设置AggregationType的user_id、date、age、sex称为Key,而设置了AggregationType的称为 Value。
(2)当导入数据时,对于Key列相同的行会聚合成一行,而Value 列会按照设置的AggregationType进行聚合。AggregationType 目前有以下几种聚合方式和agg_state:
- SUM:求和,多行的Value进行累加
- REPLACE:替代,下一批数据中的Value会替换之前导入过的行中的Value
- MAX:保留最大值
- MIN:保留最小值
- REPLACE_IF_NOT_NULL:非空值替换。和REPLACE的区别在于对于null值,不做替换
- HLL_UNION:HLL类型的列的聚合方式,通过HyperLogLog算法聚合 (?)
- BITMAP_UNION:BIMTAP类型的列的聚合方式,进行位图的并集聚合 (?)
3、导入数据
(1)假设有以下导入数据(原始数据)
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017/10/1 | 北京 | 20 | 0 | 2017/10/1 6:00 | 20 | 10 | 10 |
10000 | 2017/10/1 | 北京 | 20 | 0 | 2017/10/1 7:00 | 15 | 2 | 2 |
10001 | 2017/10/1 | 北京 | 30 | 1 | 2017/10/1 17:05 | 2 | 22 | 22 |
10002 | 2017/10/2 | 上海 | 20 | 1 | 2017/10/2 12:59 | 200 | 5 | 5 |
10003 | 2017/10/2 | 广州 | 32 | 0 | 2017/10/2 11:20 | 30 | 11 | 11 |
10004 | 2017/10/1 | 深圳 | 35 | 0 | 2017/10/1 10:00 | 100 | 3 | 3 |
10004 | 2017/10/3 | 深圳 | 35 | 0 | 2017/10/3 10:20 | 11 | 6 | 6 |
insert into example_tbl_agg1 values
(10000,"2017-10-01","北京",20,0,"2017-10-01 06:00:00",20,10,10),
(10000,"2017-10-01","北京",20,0,"2017-10-01 07:00:00",15,2,2),
(10001,"2017-10-01","北京",30,1,"2017-10-01 17:05:45",2,22,22),
(10002,"2017-10-02","上海",20,1,"2017-10-02 12:59:12",200,5,5),
(10003,"2017-10-02","广州",32,0,"2017-10-02 11:20:00",30,11,11),
(10004,"2017-10-01","深圳",35,0,"2017-10-01 10:00:15",100,3,3),
(10004,"2017-10-03","深圳",35,0,"2017-10-03 10:20:22",11,6,6);
(2)Doris中最终存储如下:
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017/10/1 | 北京 | 20 | 0 | 2017/10/1 7:00 | 35 | 10 | 2 |
10001 | 2017/10/1 | 北京 | 30 | 1 | 2017/10/1 17:05 | 2 | 22 | 22 |
10002 | 2017/10/2 | 上海 | 20 | 1 | 2017/10/2 12:59 | 200 | 5 | 5 |
10003 | 2017/10/2 | 广州 | 32 | 0 | 2017/10/2 11:20 | 30 | 11 | 11 |
10004 | 2017/10/1 | 深圳 | 35 | 0 | 2017/10/1 10:00 | 100 | 3 | 3 |
10004 | 2017/10/3 | 深圳 | 35 | 0 | 2017/10/3 10:20 | 11 | 6 | 6 |
- 经过聚合,Doris中最终只会存储聚合后的数据。换句话说,即明细数据会丢失,用户不能够再查询到聚合前的明细数据了
- 在本例子中,没有设置AggregationType的user_id、date、age、sex称为Key,当导入数据时,对于Key列相同的行才会聚合成一行
4、agg_state (?)
AGG_STATE不能作为key列使用,建表时需要同时声明聚合函数的签名。用户不需要指定长度和默认值。实际存储的数据大小与函数实现有关。
(1)建表
set enable_agg_state=true;
create table aggstate(
k1 int null,
k2 agg_state<sum(int)> generic,
k3 agg_state<group_concat(string)> generic
)
aggregate key (k1)
distributed BY hash(k1) buckets 3
properties("replication_num" = "1");
- 其中agg_state用于声明数据类型为agg_state,sum/group_concat为聚合函数的签名。注意agg_state是一种数据类型,同int/array/string
- agg_state只能配合state /merge/union函数组合器使用
- agg_state是聚合函数的中间结果,例如,聚合函数sum,则 agg_state可以表示sum(1,2,3,4,5)的这个中间状态,而不是最终的结果
- agg_state类型需要使用state函数来生成,对于当前的这个表,则为sum_state,group_concat_state
(2)导入数据
insert into aggstate values(1,sum_state(1),group_concat_state('a'));
insert into aggstate values(1,sum_state(2),group_concat_state('b'));
insert into aggstate values(1,sum_state(3),group_concat_state('c'));
此时表只有一行(注意,下面的表只是示意图,不是真的可以 select显示出来)
k1 | k2 | k3 |
---|---|---|
1 | sum(1,2,3) | group_concat_state(a,b,c) |
再插入一条数据
insert into aggstate values(2,sum_state(4),group_concat_state('d'));
此时表的结构:
k1 | k2 | k3 |
---|---|---|
1 | sum(1,2,3) | group_concat_state(a,b,c) |
2 | sum(4) | group_concat_state(d) |
(3)可以通过 merge 操作来合并多个 state,并且返回最终聚合函数计算的结果
select sum_merge(k2) from aggstate;
10
(4)如果不想要聚合的最终结果,可以使用union来合并多个聚合的中间结果,生成一个新的中间结果。
insert into aggstate select 3,sum_union(k2),group_concat_union(k3) from aggstate ;
k1 | k2 | k3 |
---|---|---|
1 | sum(1,2,3) | group_concat_state(a,b,c) |
2 | sum(4) | group_concat_state(d) |
3 | sum(1,2,3,4) | group_concat_state(a,b,c,d) |
四、使用注意
- Key列必须在所有Value列之前
- 尽量选择整型类型
- 对于不同长度的整型类型的选择原则,遵循够用即可
- 对于 VARCHAR 和 STRING 类型的长度,遵循够用即可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具