一、背景
目前 Starrocks 根据摄入数据和实际存储数据之间的映射关系,分为明细模型(Duplicate key)、聚合模型(Aggregate key)、更新模型(Unique key)和主键模型(Primary key)。
二、明细模型
Starrocks 建表默认采用明细模型,排序列使用稀疏索引,可以快速过滤数据。明细模型用于保存所有历史数据,并且用户可以考虑讲过了条件中频繁使用的维度列作为排序键,比如用户经常需要查看某一时间,可以将事件事件和事件类型作为排序键。
使用例子:
(1)建表,在建表时指定模型和排序键
sql
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
(2)插入测试数据
sql
INSERT INTO detail VALUES('2021-11-18 12:00:00.00',1,1001,1,1);
INSERT INTO detail VALUES('2021-11-17 12:00:00.00',2,1001,1,1);
INSERT INTO detail VALUES('2021-11-16 12:00:00.00',3,1001,1,1);
INSERT INTO detail VALUES('2021-11-15 12:00:00.00',1,1001,1,1);
INSERT INTO detail VALUES('2021-11-14 12:00:00.00',2,1001,1,1);
(3)查询数据,5条明细数据都在。此种模型的表用来存储所有历史明细数据。

三、聚合模型
在数据分析中,很多场景需要基于明细数据进行统计和汇总,这个时候就可以使用聚合模型了。比如:统计 app 访问量、用户访问时长、用户访问次数、展示总量、消费统计等场景。适合聚合模型来分析的业务场景主要有以下特点:
- 业务方进行查询为汇总类查询,比如 sum、count、max 等;
- 不需要查看原始明细数据;
- 老数据不会被频繁修改,只会追加和新增;
使用例子:
(1)建表,在建表时指定模型和排序键
sql
CREATE TABLE IF NOT EXISTS 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",
mt BIGINT MAX)
DISTRIBUTED BY HASH(site_id) BUCKETS 8;
(2)插入测试数据
sql
INSERT INTO aggregate_tbl VALUES(1001,'2021-11-18 12:00:00.00',100,1,5);
INSERT INTO aggregate_tbl VALUES(1001,'2021-11-18 12:00:00.00',100,1,10);
INSERT INTO aggregate_tbl VALUES(1001,'2021-11-18 12:00:00.00',100,1,15);
INSERT INTO aggregate_tbl VALUES(1001,'2021-11-18 12:00:00.00',100,1,100);
INSERT INTO aggregate_tbl VALUES(1001,'2021-11-18 12:00:00.00',100,1,20);
INSERT INTO aggregate_tbl VALUES(1002,'2021-11-18 12:00:00.00',100,1,5);
INSERT INTO aggregate_tbl VALUES(1002,'2021-11-18 12:00:00.00',100,3,25);
INSERT INTO aggregate_tbl VALUES(1002,'2021-11-18 12:00:00.00',100,1,15);
(3)查询测试数据,可以看到pv是sum累计的值,mt是明细中最大的值。如果只需要查看聚合后的指标,那么使用此种模型将会大大减少存储的数据量。

四、更新模型
有些分析场景下,数据需要进行更新,比如拉链表。Starrocks 则采用更新模型来满足这种需求。比如,电商场景下,订单的状态经常会改变,每天的订单更新数量可以突破上亿。这种业务场景下,可以使用更新模型来满足分析需求。但是如果用户需要更加实时/频繁的更新操作,建议使用主键模型。使用更新模型的场景特点:
- 已经写入的数据有大量的更新需求;
- 需要进行实时数据分析;
使用例子:
(1)建表,指定更新模型;
sql
CREATE TABLE IF NOT EXISTS update_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
(2)插入测试数据,注意:现在是指定create_time和order_id为唯一键,那么相同日期相同订单的数据会进行覆盖操作;
sql
INSERT INTO update_detail VALUES('2011-11-18',1001,1,1000);
INSERT INTO update_detail VALUES('2011-11-18',1001,2,2000);
INSERT INTO update_detail VALUES('2011-11-17',1001,2,500);
INSERT INTO update_detail VALUES('2011-11-18',1002,3,3000);
INSERT INTO update_detail VALUES('2011-11-18',1002,4,4500);
(3)查询结果,可以看到如果日期和订单相同则会进行覆盖操作。

五、主键模型
相比较更新模型,主键模型可以更好地支持实时/频繁更新的功能。虽然更新模型也可以实现实时对数据的更新,但是更新模型采用 Merge on Read 读时合并策略会大大限制查询功能,在主键模型更好地解决了行级的更新操作。配合 Flink-connector-starrocks 可以完成 Mysql CDC 实时同步的方案。
需要注意的是:由于存储引擎会为主键建立索引,导入数据时会把索引加载到内存中,所以主键模型对内存的要求更高,所以不适合主键模型的场景还是比较多的。目前比较适合使用主键模型的场景有这两种:
- 数据冷热特征:比如最近几天的数据才需要修改,旧的冷数据很少需要修改,比如订单数据,老的订单完成后就不再更新,并且分区是按天进行分区的,那么在导入数据时,历史分区的数据的主键就不会被加载,也就不会占用内存了,内存中仅会加载近几天的索引;
- 大宽表(数百列数千列):主键只占整个数据的很小一部分,内存开销比较低。比如用户状态 / 画像表,虽然列非常多,但总的用户数量不大,主键索引内存占用相对可控;
原理:
由于更新模型采用 Merge 策略,使得谓词无法下推和索引无法使用,严重影响查询性能。所以主键模型通过主键约束,保证同一个主键仅存一条数据的记录,这样就规避了 Merge 操作。
StarRocks 收到对某记录的更新操作时,会通过主键索引找到该条数据的位置,并对其标记为删除,再插入一条数据,相当于把 update 改写为 delete+insert;
使用例子:
(1)建表,指定主键模型;
sql
CREATE TABLE users (
user_id BIGINT NOT NULL,
NAME STRING NOT NULL,
email STRING NULL,
address STRING NULL,
age TINYINT NULL,
sex TINYINT NULL
) PRIMARY KEY (user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 4
(2)插入测试数据,和更新模型类似,当 user_id 相同发送冲突时会进行覆盖;
sql
INSERT INTO users VALUES(1001,'张三','111@qq.com','AAA',17,'0');
INSERT INTO users VALUES(1001,'李四','222@qq.com','BBB',18,'1');
INSERT INTO users VALUES(1002,'aaa','222@qq.com','aaa',18,'0');
INSERT INTO users VALUES(1002,'bbb','222@qq.com','bbb',18,'1');
(3)查询数据;

六、排序键
Starrocks 为加速查询,在内部组织并存储数据时,会把表中数据按照指定的列进行排序,这部分用于排序的列(一个或多个)称之为 Sort Key。明细模型中 Sort Key 就是指定的用于排序的列(即 DUMOKLICATE KEY 指定的列);聚合模型中 Sort Key 就是用于聚合的列(即 AGGREGATE 指定的列);更新模型中 Sort Key 就是指定的满足唯一性约束的列(即 UNIQUE KEY 指定的列)。创建排序列需要注意以下两点:
- 排序列的定义必须出现在建表语句中其他列的定义之前;
- 排序列的顺序是由建表语句中的列顺序决定的;
使用排序键时有以下注意事项:
- 用户查询时如果条件包含上述两列,则可以大幅降低扫描数据行;
- 如果查询只包含排序键中的第一列,也能定位到只包含这一列值的数据行;
- 如果查询只包含排序键中的后面一列,那么就需要扫描所有的数据行,排序的效果大打折扣;
使用排序键的本质就是二分查找,所以排序列指定得越多,那么消耗得内存也会越大,所以 Starrocks 为了避免这种情况的发生,对排序键做了以下限制:
- shortkey 的列只能是排序键的前缀;
- shortkey 列数不超过 3;
- 字节数不超过36字节;
- 不包含 FLOAT / DOUBLE 类型的列;
- VARCHAR 类型列只能出现一次, 并且是末尾位置;
- 当 shortkey index 的末尾列为 CHAR 或者 VARCHAR 类型时, shortkey 的长度会超过 36 字节;
- 当用户在建表语句中指定 PROPERTIES {short_key = "integer"} 时, 可突破上述限制;
七、物化视图
Materialized Views 表:简称 MVs,物化视图。使用场景:在实际的业务场景中,通常存在两种场景并存的分析需求:对固定维度的聚合分析 和对原始明细数据任意维度的分析。例如,在销售场景中,每条订单数据包含这几个维度信息(item_id, sold_time, customer_id, price)。在这种场景下,有两种分析需求并存:
- 业务方需要获取某个商品在某天的销售额是多少,那么仅需要在维度(item_id, sold_time)维度上对 price 进行聚合即可;
- 分析某个人在某天对某个商品的购买明细数据;
在现有的 StarRocks 数据模型中,如果仅建立一个聚合模型的表,比如(item_id, sold_time, customer_id, sum(price))。由于聚合损失了数据的部分信息,无法满足用户对明细数据的分析需求。如果仅建立一个 Duplicate 模型,虽可以满足任意维度的分析需求,但由于不支持 Rollup,分析性能不佳,无法快速完成分析。如果同时建立一个聚合模型和一个 Duplicate 模型,虽可以满足性能和任意维度分析,但两表之间本身无关联,需要业务方自行选择分析表。不灵活也不易用。
7.1 如何使用物化视图
使用聚合函数(如 sum 和 count)的查询,在已经包含聚合数据的表中可以更高效地执行。这种改进的效率对于查询大量数据尤其适用。表中的数据已经被物化在存储节点中,并且在增量更新中能和 Base 表保持一致。用户创建物化视图后,查询优化器支持选择一个最高效的物化视图映射,并直接对物化视图进行查询而不是 Base 表。由于物化视图数据通常比 Base 表数据小很多,因此物化视图的查询速度会快很多。下面有个例子:
(1)创建测试物化视图;
sql
CREATE MATERIALIZED VIEW test_detail_view
AS SELECT user_id,MAX(event_type),COUNT(device_code),SUM(channel) FROM detail GROUP BY user_id;
(2)建完视图后,用户并不能感知创建是否成功,可以通过 explain 来分析是否命中视图。可以看到上面物化视图对 event_type 字段使用 max 函数,那么 rollup 命中的数据源为创建的物化视图;
sql
mysql> explain select max(event_type) from detail;

(3)那么如果使用对 event_type 字段使用 count 函数,又可以看到 rollup 命中的是 detail 表;

(4)那么建立物化视图,就可以帮助用户对于不同场景都起到加速查询的作用。目前物化视图支持的函数如下有:count、max、min、sum、percentile_approx、hill_union、bitmap_union;