了解 Doris 数据模型对于我们使用 Doris 来解决我们业务问题非常重要,这个系列我们将详细介绍 Doris 的三种数据模型及 Doris 数据分区分桶的一些策略,帮助用户更好的使用 Doris 。
这个系列我会讲解 Doris 的三种数据模型及在这三种数据模型之上的 Rollup,物化视图及前缀索引。还有在这个三种数据模型之上的数据分区分桶的策略。
我们知道在 Doris 中,数据以表(Table)的形式进行逻辑上的描述。 一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。
列可以划分为两类:键 Key 和值 Value。从业务角度来看,键和值对应的就是维度列和指标列
Doris 针对不同场景提供了三种数据模型
一个正常的模型它肯定会把明细的数据存储在一个数据库中,也就是存在 Doris 中。但是因为 Doris 它最早是给凤巢的一个广告报表做的,广告报表有一个很大的特点,就是它只关心统计分析的结果,而不太关心明细的数据,所以 Doris 最早一代的数据模型,是一个聚合的模型。
聚合模型的特点就是将表中的列分为了Key和Value两种。 Key 就是数据的维度列,比如时间,地区等等。 Value 则是数据的指标列,比如点击量,花费等。每个指标列还会有自己的聚合函数,包括sum、min、max和bitmap_union 等。数据会根据维度列进行分组,并对指标列进行聚合。如下图:
通过上面的图我们可以看到,这是一个典型的用户信息和访问行为的事实表。 在一般星型模型中,用户信息和访问行为一般分别存放在维度表和事实表中。这里我们为了更加方便的解释 Doris 的数据模型,将两部分信息统一存放在一张表中。
这个表我们是按照:user_id,date,city,age,sex 来统计用户最后访问时间、用户总消费、用户最大停留时间、最小停留时间
表中的列按照是否设置了 IndexKeysType
是 AGG_KEYS
表示是聚合模型,分为 Key (维度列) 和 Value(指标列)。
这里我们 Key
列是 true
表示这个字段是 Key 列,false 的表示 Value 列,所有的 value 列我们在建表的时候指定他们的聚合类型(AggregationType)
上面这个对应的建表语句如下:
CREATE TABLE example_tbl_02
(
`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"
);
我们插入几条数据:
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
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 |
上面这个数据中我们可以看到,前两行的数据 Key 是完全一致的,后面 Value 字段应该按照我们建表时候指定的聚合方式进行自动完成数据聚合,我们执行下面的语句插入数据,看看是否和我们预想的一致:
insert into example_tbl_02 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);
通过下图来查看我们最后执行后的数据
当我们导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的 AggregationType
进行聚合。 AggregationType
目前有以下几种聚合方式和agg_state:
我们知道需要再建表的时候指定数据模型,一旦创建表后期不能修改数据模型,如果你建表的时候没有指定数据模型默认是明细模型 (Duplicate Key).
在聚合模型中,模型对外展现的,是最终聚合后的数据。也就是说,任何还未聚合的数据(比如说两个不同导入批次的数据),必须通过某种方式,以保证对外展示的一致性,特别是在聚合模型上做count计算,可能会导致结果不准确,针对这种情况我们怎么去解决。
第一种方式:增加一个 count 列,并且导入数据中,该列值恒为 1。则 select count(*) from table;
的结果等价于 select sum(count) from table;
。而后者的查询效率将远高于前者。不过这种方式也有使用限制,就是用户需要自行保证,不会重复导入 AGGREGATE KEY 列都相同的行。否则,select sum(count) from table;
只能表述原始导入的行数,而不是 select count(*) from table;
的语义。
另一种方式:就是 将如上的 count
列的聚合类型改为 REPLACE,且依然值恒为 1。那么 select sum(count) from table;
和 select count(*) from table;
的结果将是一致的。并且这种方式,没有导入重复行的限制。