ROLLUP 在多维分析中是 “上卷” 的意思,即将数据按某种指定的粒度进行进一步聚合。
在 Doris 中,我们将用户通过建表语句创建出来的表称为 Base 表(Base Table
)。Base 表中保存着按用户建表语句指定的方式存储的基础数据。
在 Base 表之上,我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的,并且在物理上是独立存储的。
ROLLUP 表的基本作用,在于在 Base 表的基础上,获得更粗粒度的聚合数据。
因为 Uniq 只是 Aggregate 模型的一个特例,所以这里我们不加以区别。
CREATE TABLE IF NOT EXISTS test_db.example_site_visit2
(
`user_id` LARGEINT NOT NULL COMMENT "用户 id",
`date` DATE NOT NULL COMMENT "数据灌入日期时间",
`timestamp` DATETIME 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`, `timestamp`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;
(1)查看表的结构信息。
desc example_site_visit2 all;
(2)比如需要查看某个用户的总消费,那么可以建立一个只有 user_id
和 cost
的 ROLLUP。
alter table example_site_visit2 add rollup rollup_cost_userid(user_id,cost);
(3)查看表的结构信息。
desc example_site_visit2 all;
(4)然后可以通过 explain
查看执行计划,是否使用到了 ROLLUP。Doris 会自动命中这个 ROLLUP 表,从而只需扫描极少的数据量,即可完成这次聚合查询。
explain SELECT user_id, sum(cost) FROM example_site_visit2 GROUP BY user_id;
(5)通过命令查看完成状态
SHOW ALTER TABLE ROLLUP;
(1)我们在 Base 表基础之上,再创建一个 ROLLUP。
alter table example_site_visit2 add rollup rollup_city_age_cost_maxd_mind(city,age,cost,max_dwell_time,min_dwell_time);
(2)当我们进行如下这些查询时,Doris 执行这些 SQL 时会自动命中这个 ROLLUP 表。
explain SELECT city, age, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM example_site_visit2 GROUP BY city, age;
explain SELECT city, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM example_site_visit2 GROUP BY city;
explain SELECT city, age, sum(cost), min(min_dwell_time) FROM example_site_visit2 GROUP BY city, age;
(3)通过命令查看完成状态。
SHOW ALTER TABLE ROLLUP;
因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了 “上卷” 这一层含义,而仅仅是作为调整列顺序,以命中前缀索引的作用。下面详细介绍前缀索引,以及如何使用 ROLLUP 改变前缀索引,以获得更好的查询效率。
不同于传统的数据库设计,Doris 不支持在任意列上创建索引。Doris 这类 MPP 架构的 OLAP 数据库,通常都是通过提高并发,来处理大量数据的。
本质上,Doris 的数据存储在类似 SSTable
(Sorted String Table
)的数据结构中。该结构是一种有序的数据结构,可以按照指定的列进行排序存储。在这种数据结构上,以排序列作为条件进行查找,会非常的高效。
在 Aggregate、Uniq 和 Duplicate 三种数据模型中。底层的数据存储,是按照各自建表语句中,AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY 中指定的列进行排序存储的。而前缀索引,即在排序的基础上,实现的一种根据给定前缀列,快速查询数据的索引方式。
我们将一行数据的前 36 36 36 个字节作为这行数据的前缀索引。当遇到 VARCHAR 类型时,前缀索引会直接截断。举例说明:
(1)以下表结构的前缀索引为 user_id
( 8 8 8 Bytes)+ age
( 4 4 4 Bytes)+ message
(前 20 20 20 Bytes)。
ColumnName | Type |
---|---|
user_id |
BIGINT |
age |
INT |
message |
VARCHAR(100) |
max_dwell_time |
DATETIME |
min_dwell_time |
DATETIME |
(2)而以下表结构的前缀索引为 user_name
( 20 20 20 Bytes)。即使没有达到 36 36 36 个字节,因为遇到 VARCHAR,所以直接截断,不再往后继续。
ColumnName | Type |
---|---|
user_name |
VARCHAR(20) |
age |
INT |
message |
VARCHAR(100) |
max_dwell_time |
DATETIME |
min_dwell_time |
DATETIME |
当我们的查询条件,是前缀索引的前缀时,可以极大的加快查询速度。比如在第一个例子中,我们执行如下查询:
SELECT * FROM table WHERE user_id=1829239 and age=20;
该查询的效率会远高于如下查询:
SELECT * FROM table WHERE age=20;
所以在建表时,正确的选择列顺序,能够极大地提高查询效率。
因为建表时已经指定了列顺序,所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说,效率上可能无法满足需求。因此,我们可以通过创建 ROLLUP 来人为的调整列顺序。举例说明。
Base 表结构如下:
ColumnName | Type |
---|---|
user_id |
BIGINT |
age |
INT |
message |
VARCHAR(100) |
max_dwell_time |
DATETIME |
min_dwell_time |
DATETIME |
我们可以在此基础上创建一个 ROLLUP 表:
ColumnName | Type |
---|---|
age |
INT |
user_id |
BIGINT |
message |
VARCHAR(100) |
max_dwell_time |
DATETIME |
min_dwell_time |
DATETIME |
可以看到,ROLLUP 和 Base 表的列完全一样,只是将 user_id
和 age
的顺序调换了。那么当我们进行如下查询时:
SELECT * FROM table where age=20 and message LIKE "%error%";
会优先选择 ROLLUP 表,因为 ROLLUP 的前缀索引匹配度更高。
ROLLUP 最根本的作用是提高某些查询的查询效率(无论是通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。因此 ROLLUP 的含义已经超出了 “上卷” 的范围。这也是为什么在源代码中,将其命名为 Materialized Index
(物化索引)的原因。
ROLLUP 是附属于 Base 表的,可以看做是 Base 表的一种辅助数据结构。用户可以在 Base 表的基础上,创建或删除 ROLLUP,但是不能在查询中显式的指定查询某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。
ROLLUP 的数据是独立物理存储的。因此,创建的 ROLLUP 越多,占用的磁盘空间也就越大。同时对导入速度也会有影响(导入的 ETL 阶段会自动产生所有 ROLLUP 的数据),但是不会降低查询效率(只会更好)。
ROLLUP 的数据更新与 Base 表是完全同步的。用户无需关心这个问题。
ROLLUP 中列的聚合方式,与 Base 表完全相同。在创建 ROLLUP 无需指定,也不能修改。
查询能否命中 ROLLUP 的一个必要条件(非充分条件)是,查询所涉及的所有列(包括 select list 和 where 中的查询条件列等)都存在于该 ROLLUP 的列中。否则,查询只能命中 Base 表。
某些类型的查询(如 count(*)
)在任何条件下,都无法命中 ROLLUP。
可以通过 EXPLAIN your_sql
命令获得查询执行计划,在执行计划中,查看是否命中 ROLLUP。
可以通过 DESC tbl_name ALL
语句显示 Base 表和所有已创建完成的 ROLLUP。