[TOC] 目前,DorisDB根据摄入数据和实际存储数据之间的映射关系, 将数据表的明细表, 聚合表和更新表, 分别对应有明细模型, 聚合模型和更新模型。为了描述方便, 我们借鉴关系模式中的主键概念, 称DorisDB表的维度列的取值构成数据表的排序键, DorisDB的排序键对比传统的主键具有: * 数据表所有维度列构成排序键, 所以后文中提及的排序列, key列本质上都是维度列. * 排序键可重复, 不必满足唯一性约束. * 数据表的每一列, 以排序键的顺序, 聚簇存储. * 排序键使用稀疏索引. 对于摄入(ingest)的主键重复的多行数据, 填充于(populate)数据表中时, 按照三种处理方式划分: * 明细模型:  表中存在主键重复的数据行, 和摄入数据行一一对应, 用户可以召回所摄入的全部历史数据. * 聚合模型:  表中不存在主键重复的数据行, 摄入的主键重复的数据行合并为一行, 这些数据行的指标列通过聚合函数合并, 用户可以召回所摄入的全部历史数据的累积结果, 但无法召回全部历史数据. * 更新模型:  聚合模型的特殊情形, 主键满足唯一性约束, 最近摄入的数据行, 替换掉其他主键重复的数据行. 相当于在聚合模型中, 为数据表的指标列指定的聚合函数为REPLACE, REPLACE函数返回一组数据中的最新数据. 需要注意: * 建表语句, 排序列的定义必须出现在指标列定义之前. * 排序列在建表语句中的出现次序为数据行的多重排序的次序. * 排序键的稀疏索引(shortkey index)会选择排序键的若干前缀列. 本章介绍特种模型的特点和使用场景, 帮助用户选择匹配业务需求的最佳模型. ### 3.2.1 明细模型 **1\. 适用场景** DorisDB建表的默认模型是明细模型。 <br> 一般用明细模型来处理的场景有如下特点: 1. 需要保留原始的数据(例如原始日志,原始操作记录等)来进行分析; 2. 查询方式灵活, 不局限于预先定义的分析方式, 传统的预聚合方式难以命中; 3. 数据更新不频繁。导入数据的来源一般为日志数据或者是时序数据,  以追加写为主要特点, 数据产生后就不会发生太多变化。 **2\. 模型原理** 用户可以指定数据表的排序列, 没有明确指定的情况下, 那么DorisDB会为表选择默认的几个列作为排序列。这样,在查询中,有相关排序列的过滤条件时,DorisDB能够快速地过滤数据,降低整个查询的时延。 注意:在向DorisDB明细模型表中导入完全相同的两行数据时,DorisDB会认为是两行数据。 **3\. 如何使用** 数据表默认采用明细模型. 排序列使用shortkey index,  可快速过滤数据.  用户可以考虑将过滤条件中频繁使用的维度列的定义放置其他列的定义之前. 例如用户经常查看某时间范围的某一类事件的数据,可以将事件时间和事件类型作为排序键。 <br> 以下是一个使用明细模型创建数据表的例子 * 其中DUPLICATE KEY(event\_time, event\_type)说明采用明细模型, 并且指定了排序键, 并且排序列的定义在其他列定义之前. ~~~ CREATE TABLE IF NOT EXISTS detail ( event_time DATETIME NOT NULL COMMENT "datetime of event", event_type INT NOT NULL COMMENT "type of event", user_id INT COMMENT "id of user" device_code INT COMMENT "device of ", channel INT COMMENT "" ) DUPLICATE KEY(event_time, event_type) DISTRIBUTED BY HASH(user_id) BUCKETS 8 ~~~ **4\. 注意事项** 1. 充分利用排序列,在建表时将经常在查询中用于过滤的列放在表的前面,这样能够提升查询速度。 2. 明细模型中, 可以指定部分的维度列为排序键; 而聚合模型和更新模型中, 排序键只能是全体维度列. <br> ### 3.2.2 聚合模型 **1\. 适用场景** 在数据分析领域,有很多需要对数据进行统计和汇总操作的场景。比如: * 分析网站或APP访问流量,统计用户的访问总时长、访问总次数; * 广告厂商为广告主提供的广告点击总量、展示总量、消费统计等; * 分析电商的全年的交易数据, 获得某指定季度或者月份的, 各人口分类(geographic)的爆款商品. 适合采用聚合模型来分析的场景具有如下特点: 1. 业务方进行的查询为汇总类查询,比如sum、count、 max等类型的查询; 2. 不需要召回原始的明细数据; 3. 老数据不会被频繁更新,只会追加新数据。 **2\. 模型原理** DorisDB会将指标列按照相同维度列进行聚合。当多条数据具有相同的维度时,DorisDB会把指标进行聚合。从而能够减少查询时所需要的处理的数据量,进而提升查询的效率。 <br> 以下面的原始数据为例: | Date | Country | PV | | :---: | :---: | :---: | | 2020.05.01 | CHN | 1 | | 2020.05.01 | CHN | 2 | | 2020.05.01 | USA | 3 | | 2020.05.01 | USA | 4 | 在DorisDB聚合模型的表中,存储内容会从四条数据变为两条数据。这样在后续查询处理的时候,处理的数据量就会显著降低: | Date | Country | PV | | :---: | :---: | :---: | | 2020.05.01 | CHN | 3 | | 2020.05.01 | USA | 7 | **3\. 如何使用** 在建表时, 只要给指标列的定义指明聚合函数, 就会启用聚合模型; 用户可以使用AGGREGATE KEY显示地定义排序建. 以下是一个使用聚合模型创建数据表的例子: * site\_id, date, city\_code为排序键; * pv为指标列, 使用聚合函数SUM. ~~~ CREATE TABLE IF NOT EXISTS example_db.aggregate_tbl ( site_id LARGEINT NOT NULL COMMENT "id of site", date DATE NOT NULL COMMENT "time of event", city_code VARCHAR(20) COMMENT "city_code of user" pv BIGINT SUM DEFAULT "0" COMMENT "total page views" ) DISTRIBUTED BY HASH(site_id) BUCKETS 8; ~~~ **4\. 注意事项** 1. 聚合表中数据会分批次多次导入, 每次导入会形成一个版本. 相同排序键的数据行聚合有三种触发方式: 1. 数据导入时, 数据落盘前的聚合; 2. 数据落盘后, 后台的多版本异步聚合; 3. 数据查询时, 多版本多路归并聚合. 2. 数据查询时, 指标列采用先聚合后过滤的方式, 把没必有做指标的列存储为维度列. 3. 聚合模型所支持的聚合函数列表请参考《Create Table语句说明》。 <br> ### 3.2.3 更新模型 **1\. 适用场景** 有些分析场景之下,数据会更新, DorisDB采用更新模型来满足这种需求。比如在电商场景中,定单的状态经常会发生变化,每天的订单更新量可突破上亿。在这种量级的更新场景下进行实时数据分析,如果在明细模型下通过delete+insert的方式,是无法满足频繁更新需求的; 因此, 用户需要使用更新模型来满足数据分析需求。 <br> 以下是一些适合更新模型的场景特点: 1. 已经写入的数据有大量的更新需求; 2. 需要进行实时数据分析。 **2\. 模型原理** 更新模型中, 排序键满足唯一性约束, 成为主键. DorisDB存储内部会给每一个批次导入数据分配一个版本号, 同一主键的数据可能有多个版本, 查询是, 最大(最新)版本的数据胜出. | ID | value | _version | | :---: | :---: | :---: | | 1 | 100 | 1 | | 1 | 101 | 2 | | 2 | 100 | 3 | | 2 | 101 | 4 | | 2 | 102 | 5 | 具体的示例如上表所示,ID是表的主键,value是表的内容,而\_\_version是DorisDB内部的版本号。其中ID为1的数据有两个导入批次,版本分别为1,2;ID为2的数据有三个批次导入,版本分别为3,4,5。在查询的时候对于ID为1只会返回最新版本2的数据,而对于ID为2只会返回最新版本5的数据,那么对于用户能能够看到的数据如下表所示: | ID | value | | :---: | :---: | | 1 | 101 | | 2 | 102 | 通过这种机制,DorisDB可以支持对于频繁更新数据的分析。 **3\. 如何使用** 在电商订单分析场景中,经常根据订单状态进行的统计分析。因为订单状态经常改变,而create\_time和order\_id不会改变,并且经常会在查询中作为过滤条件。所以可以将 create\_time和order\_id 两个列作为这个表的主键(即,在建表时用UNIQUE KEY关键字定义),这样既能够满足订单状态的更新需求,又能够在查询中进行快速过滤。 <br> 以下是一个使用更新模型创建数据表的例子: * 用UNIQUE KEY(create\_time, order\_id)做主键, 其中create\_time, order\_id为排序列, 其定义在其他列定义之前出现; * order\_state和total\_price为指标列, 其聚合类型为REPLACE. ~~~ CREATE TABLE IF NOT EXISTS detail ( create_time DATE NOT NULL COMMENT "create time of an order", order_id BIGINT NOT NULL COMMENT "id of an order", order_state INT COMMENT "state of an order", total_price BIGINT COMMENT "price of an order" ) UNIQUE KEY(create_time, order_id) DISTRIBUTED BY HASH(order_id) BUCKETS 8 ~~~ **4\. 注意事项** 1. 导入数据时需要将所有字段补全才能够完成更新操作,即,上述例子中的create\_time、order\_id、order\_state和total\_price四个字段都需必须存在。 2. 对于更新模型的数据读取,需要在查询时完成多版本合并,当版本过多时会导致查询性能降低。所以在向更新模型导入数据时,应该适当降低导入频率,从而提升查询性能。建议在设计导入频率时以满足业务对实时性的要求为准。如果业务对实时性的要求是分钟级别,那么每分钟导入一次更新数据即可,不需要秒级导入。 3. 在查询时,对于value字段的过滤通常在多版本合并之后。将经常过滤字段且不会被修改的字段放在主键上, 能够在合并之前就将数据过滤掉,从而提升查询性能。 4. 因为合并过程需要将所有主键字段进行比较,所以应该避免放置过多的主键字段,以免降低查询性能。如果某个字段只是偶尔会作为查询中的过滤条件存在,不需要放在主键中。