了解 Doris 数据模型对于我们使用 Doris 来解决我们业务问题非常重要,这个系列我们将详细介绍 Doris 的三种数据模型及 Doris 数据分区分桶的一些策略,帮助用户更好的使用 Doris 。
这个系列我会讲解 Doris 的三种数据模型及在这三种数据模型之上的 Rollup,物化视图及前缀索引。还有在这个三种数据模型之上的数据分区分桶的策略。
我们知道在 Doris 中,数据以表(Table)的形式进行逻辑上的描述。 一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。
列可以划分为两类:键 Key 和值 Value。从业务角度来看,键和值对应的就是维度列和指标列
Doris 针对不同场景提供了三种数据模型
- Aggregate (聚合模型) : 将表中的列分为了Key和Value两种,数据会根据维度列进行分组,并对指标列进行聚合。
- Unique (唯一主键模型):这个模型一般我们是在对接业务系统 RDS 的时候,需要 Doris 数据和业务数据保持一致,支持Upsert、Delete等操作
- Duplicate (明细模型): 这个模型不保证数据唯一性(如果你数据重复导入两次就会重复),数据既没有主键,也没有聚合需求,一般情况下我们使用这个模型
聚合模型特点
一个正常的模型它肯定会把明细的数据存储在一个数据库中,也就是存在 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:
- SUM:求和,多行的 Value 进行累加。
- REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
- MAX:保留最大值。
- MIN:保留最小值。
- REPLACE_IF_NOT_NULL:非空值替换。和 REPLACE 的区别在于对于null值,不做替换,这种聚合方式适用于部分列更新。
- HLL_UNION:HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。
- BITMAP_UNION:BIMTAP 类型的列的聚合方式,进行位图的并集聚合。
聚合模型使用场景
我们知道需要再建表的时候指定数据模型,一旦创建表后期不能修改数据模型,如果你建表的时候没有指定数据模型默认是明细模型 (Duplicate Key).
- Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性
- 在数据分析领域,有很多需要对数据进⾏统计和汇总操作的场景,如需要分析⽹站和 APP 访问流量,统计⽤⼾的访问总时⻓,访问总次数。或者像⼚商需要为⼴告主提供⼴告点击的总流量,展⽰总量,消费统计等指标。在这些不需要保存明细数据的场景,只需要按照固定维度和指标进行聚合后的数据,通常可以使⽤聚合模型。
- 另外再部分列更新场景,如果用户既需要唯一主键约束,又需要更新部分列(例如将多张源表导入到一张 doris 表的情形),则可以考虑使用 Aggregate 模型,同时将非主键列的聚合类型设置为 REPLACE_IF_NOT_NULL,不过 Doris 2.0 版本 Unqiue Key 模型开始支持部分列更新,如果你没有开启行存情况下,大数据量表进行部分列更新对你的 CPU 消耗可能会很高,这个时候如果你没有特殊的需求,也可以使用聚合模型。Unique Key 模型其实也是聚合模型的一个特例。
- 聚合模型中不保存明细数据,如果你需要保存明细数据,就不适合使用聚合模型,建议选择Unqiue Key 或者 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;
的结果将是一致的。并且这种方式,没有导入重复行的限制。