Doris使用之数据模型
条评论内容:数据模型
基本概念
在 Doris 中,数据以表(Table)的形式进行逻辑上的描述。 一张表包括行(Row)和列(Column)。
Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。
Column 可以分为两大类:Key 和 Value。从业务角度看,Key 和 Value 可以分别对应维度列和指标列。
数据模型
Doris 的数据模型主要分为3类:
- Aggregate
- Unique
- Duplicate
Aggregate
有如下建表语句
1 | CREATE TABLE IF NOT EXISTS example_db.example_tbl |
表中的列按照是否设置了 AggregationType
,分为 Key (维度列) 和 Value(指标列)。没有设置 AggregationType
的,如 user_id
、date
、age
… 等称为
Key,而设置了 AggregationType
的称为 Value。
导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的 AggregationType
进行聚合。
AggregationType
目前有以下四种聚合方式:
- SUM:求和,多行的 Value 进行累加。
- REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
- MAX:保留最大值。
- MIN:保留最小值。
注:在同一个导入批次中的数据,对于 REPLACE 这种聚合方式,替换顺序不做保证。如在这个例子中,最终保存下来的,也有可能是
2017-10-01 06:00:00
。而对于不同导入批次中的数据,可以保证,后一批次的数据会替换前一批次。
经过聚合,Doris 中最终只会存储聚合后的数据。换句话说,即明细数据会丢失,用户不能够再查询到聚合前的明细数据了。
数据的聚合,在 Doris 中有如下三个阶段发生:
- 每一批次数据导入的 ETL 阶段。该阶段会在每一批次导入的数据内部进行聚合。
- 底层 BE 进行数据 Compaction 的阶段。该阶段,BE 会对已导入的不同批次的数据进行进一步的聚合。
- 数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合
1 | 数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程 |
Unique
适用于获取 Primary Key 唯一性约束。
在1.2版本之前,Unique本质是Aggregate模型的特例。使用读时合并方式实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16CREATE TABLE IF NOT EXISTS example_db.example_tbl
(
`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"
);等同于
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16CREATE TABLE IF NOT EXISTS example_db.example_tbl
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
`city` VARCHAR(20) REPLACE COMMENT "用户所在城市",
`age` SMALLINT REPLACE COMMENT "用户年龄",
`sex` TINYINT REPLACE COMMENT "用户性别",
`phone` LARGEINT REPLACE COMMENT "用户电话",
`address` VARCHAR(500) REPLACE COMMENT "用户地址",
`register_time` DATETIME REPLACE COMMENT "用户注册时间"
)
AGGREGATE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);在1.2版本之后,使用写时合并模式实现。通过在写入时做一些额外的工作,实现了最优的查询性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17CREATE TABLE IF NOT EXISTS example_db.example_tbl
(
`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"
);
在开启了写时合并选项的Unique表上,数据在导入阶段就会去将被覆盖和被更新的数据进行标记删除,同时将新的数据写入新的文件。在查询的时候,所有被标记删除的数据都会在文件级别被过滤掉,读取出来的数据就都是最新的数据,消除掉了读时合并中的数据聚合过程,并且能够在很多情况下支持多种谓词的下推。因此在许多场景都能带来比较大的性能提升,尤其是在有聚合查询的情况下。
Duplicate
在某些场景下,数据既没有主键,也没有聚合需求,需要数据完全按照导入文件中的数据进行存储。
1 | CREATE TABLE IF NOT EXISTS example_db.example_tbl |
这种数据模型区别于 Aggregate 和 Unique 模型。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。
建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序。更贴切的名称应该为 “Sorted Column”,这里取名 “DUPLICATE KEY” 只是用以明确表示所用的数据模型。
局限性
Aggregate
示例
初始建表语句
1 | CREATE TABLE IF NOT EXISTS example_db.example_tbl |
在灌入如下数据后
batch 1
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 50 |
10002 | 2017-11-21 | 39 |
batch 2
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 1 |
10001 | 2017-11-21 | 5 |
10003 | 2017-11-22 | 22 |
聚合后结果为
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 51 |
10001 | 2017-11-21 | 5 |
10002 | 2017-11-21 | 39 |
10003 | 2017-11-22 | 22 |
对cost取MIN
此时,对cost取MIN,返回的应该是聚合后的5
,而不是期望的1
count
此时,取count(*),返回的应该是聚合后的4
,而不是期望的5
因此最终建议如果有count需求,在建表时,添加一个无意义的count字段
1 | CREATE TABLE IF NOT EXISTS example_db.example_tbl |
这时,要获取数量,使用select sum(count) from table
即可。
由上可见,使用Aggregate模型时,为了保证语义的一致,讨论的都是聚合后的结果,不一定是期望的结果。
Unique
Unique的写时合并实现不存在以上局限
Duplicate
Duplicate不存在以上局限
key列
Duplicate、Aggregate、Unique 模型,都会在建表指定 key 列,然而实际上是有所区别的
- Duplicate 模型,表的key列,可以认为只是 “排序列”,并非起到唯一标识的作用
- Aggregate、Unique 模型这种聚合类型的表,key 列是兼顾 “排序列” 和 “唯一标识列”,是真正意义上的“ key 列”
选择建议
Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。
Unique 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势。
- 对于聚合查询有较高性能需求的用户,推荐使用自1.2版本加入的写时合并实现。
- Unique 模型仅支持整行更新,如果用户既需要唯一主键约束,又需要更新部分列(例如将多张源表导入到一张 doris 表的情形),则可以考虑使用 Aggregate 模型,同时将非主键列的聚合类型设置为 REPLACE_IF_NOT_NULL。
Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)
Ad-hoc:即席查询