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 类型的长度,遵循够用即可
posted @   一年都在冬眠  阅读(552)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示