3.doris Rollup与索引

Rollup

ROLLUP 在多维分析中是“上卷”的意思,即将数据按某种指定的粒度进行进一步聚合

基本概念

在 Doris 中,我们将用户通过建表语句创建出来的表称为 Base 表(Base Table)。Base 表中保存着按用户建表语句指定的方式存储的基础数据

在 Base 表之上,我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的,并且在物理上是独立存储

ROLLUP 表的基本作用,在于在 Base 表的基础上,获得更粗粒度的聚合数据

Rollup语法
-- 创建rollup
alter table TABLE_NAME add rollup ROLLUP_NAME(field1,field2...);
-- 查看rollup
SHOW ALTER TABLE ROLLUP;
-- 查看是否命中rollup
explain SELECT ...;
Duplicate 模型中的 ROLLUP

因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序,以命中前缀索引的作用。我们将在前缀索引详细介绍前缀索引,以及如何使用ROLLUP改变前缀索引,以获得更好的查询效率

ROLLUP使用说明

  • ROLLUP 最根本的作用是提高某些查询的查询效率(无论是通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。
  • 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。

在 Doris 里 Rollup 作为一份聚合物化视图,其在查询中可以起到两个作用:

  • 索引
  • 聚合数据(仅用于聚合模型,即aggregate key)

但为了命中 Rollup 需要满足一定的条件,并且可以通过执行计划中 ScanNode 节点的 PreAggregation 的值来判断是否可以命中 Rollup,以及 Rollup 字段来判断命中的是哪一张 Rollup 表

聚合物化视图其聚合数据的功能是必不可少的,这类物化视图对于聚合类查询或报表类查询都有非常大的帮助,要命中聚合物化视图需要下面一些前提:

  1. 查询或者子查询中涉及的所有列都存在一张独立的 Rollup 中。
  2. 如果查询或者子查询中有 Join,则 Join 的类型需要是 Inner join

如果符合上述条件,则针对聚合模型在判断命中 Rollup 的时候会有两个阶段:

  1. 首先通过条件匹配出命中前缀索引索引最长的 Rollup 表
  2. 然后比较 Rollup 的行数,选择最小的一张 Rollup

索引概述

目前 Doris 主要支持两类索引:

  1. 内建的智能索引,包括前缀索引和 ZoneMap 索引。
  2. 用户手动创建的二级索引,包括 倒排索引、 bloomfilter索引、 ngram bloomfilter索引 和bitmap索引
前缀索引

不同于传统的数据库设计,Doris 不支持在任意列上创建索引。Doris 这类 MPP 架构的 OLAP 数据库,通常都是通过提高并发,来处理大量数据的。

本质上,Doris 的数据存储在类似 SSTable(Sorted String Table)的数据结构中。该结构是一种有序的数据结构,可以按照指定的列进行排序存储。在这种数据结构上,以排序列作为条件进行查找,会非常的高效。

在 Aggregate、Unique 和 Duplicate 三种数据模型中。底层的数据存储,是按照各自建表语句中,AGGREGATE KEYUNIQUE KEYDUPLICATE KEY 中指定的列进行排序存储的。而前缀索引,即在排序的基础上,实现的一种根据给定前缀列,快速查询数据的索引方式

一行数据的前 36 个字节 作为这行数据的前缀索引。当遇到 VARCHAR 类型时,前缀索引会直接截断 遇到VARCHAR会截取前20字节

ColumnName Type
user_id BIGINT
age INT
message VARCHAR(100)
max_dwell_time DATETIME
min_dwell_time DATETIME

如上索引匹配 user_id(8 Bytes) age(4 Bytes) message(prefix 20Bytes)【遇VARCHAR截断】

ColumnName Type
user_name VARCHAR(20)
age INT
message VARCHAR(100)
max_dwell_time DATETIME
min_dwell_time DATETIME

如上索引,user_name遇到VARCHAR只会匹配前20Bytes

Rollup 来调整前缀索引

因为建表时已经指定了列顺序,所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说,效率上可能无法满足需求。因此,我们可以通过创建 ROLLUP 来人为的调整列顺序

倒排索引

doris2.0.0版本之后开始支持倒排索引,可以用来进行文本类型的全文检索、普通数值日期类型的等值范围查询,快速从海量数据中过滤出满足条件的行

原理介绍

在Doris的倒排索引实现中,table的一行对应一个文档、一列对应文档中的一个字段,因此利用倒排索引可以根据关键词快速定位包含它的行,达到WHERE子句加速的目的。

与Doris中其他索引不同的是,在存储层倒排索引使用独立的文件,跟segment文件有逻辑对应关系、但存储的文件相互独立。这样的好处是可以做到创建、删除索引不用重写tablet和segment文件,大幅降低处理开销

Doris倒排索引的功能简要介绍如下:

  • 增加了字符串类型的全文检索
    • 支持字符串全文检索,包括同时匹配多个关键字MATCH_ALL、匹配任意一个关键字MATCH_ANY、匹配短语词组MATCH_PHRASE
    • 支持字符串数组类型的全文检索
    • 支持英文、中文以及Unicode多语言分词
  • 加速普通等值、范围查询,覆盖bitmap索引的功能,未来会代替bitmap索引
    • 支持字符串、数值、日期时间类型的 =, !=, >, >=, <, <= 快速过滤
    • 支持字符串、数字、日期时间数组类型的 =, !=, >, >=, <, <=
  • 支持完善的逻辑组合
    • 新增索引对OR NOT逻辑的下推
    • 支持多个条件的任意AND OR NOT组合
  • 灵活、快速的索引管理
    • 支持在创建表上定义倒排索引
    • 支持在已有的表上增加倒排索引,而且支持增量构建倒排索引,无需重写表中的已有数据
    • 支持删除已有表上的倒排索引,无需重写表中的已有数据
创建语法
  • 建表时定义倒排索引,语法说明如下
    • USING INVERTED 是必须的,用于指定索引类型是倒排索引
    • PROPERTIES 是可选的,用于指定倒排索引的额外属性,目前有三个属性
      • parser指定分词器
        • 默认不指定代表不分词
        • english是英文分词,适合被索引列是英文的情况,用空格和标点符号分词,性能高
        • chinese是中文分词,适合被索引列主要是中文的情况,性能比english分词低
        • unicode是多语言混合类型分词,适用于中英文混合、多语言混合的情况。它能够对邮箱前缀和后缀、IP地址以及字符数字混合进行分词,并且可以对中文按字符分词。
      • parser_mode用于指定分词的模式,目前parser = chinese时支持如下几种模式:
        • fine_grained:细粒度模式,倾向于分出比较短的词,比如 ‘武汉市长江大桥’ 会分成 ‘武汉’, ‘武汉市’, ‘市长’, ‘长江’, ‘长江大桥’, ‘大桥’ 6个词
        • coarse_grained:粗粒度模式,倾向于分出比较长的词,,比如 ‘武汉市长江大桥’ 会分成 ‘武汉市’ ‘长江大桥’ 2个词
        • 默认coarse_grained
      • support_phrase用于指定索引是否支持MATCH_PHRASE短语查询加速
        • true为支持,但是索引需要更多的存储空间
        • false为不支持,更省存储空间,可以用MATCH_ALL查询多个关键字
        • 默认false
      • char_filter:功能主要在分词前对字符串提前处理
        • char_filter_type:指定使用不同功能的char_filter(目前仅支持char_replace)
          • char_replace 将pattern中每个char替换为一个replacement中的char
            • char_filter_pattern:需要被替换掉的字符数组
            • char_filter_replacement:替换后的字符数组,可以不用配置,默认为一个空格字符
      • ignore_above:控制字符串是否建索引。
        • 长度超过 ignore_above 设置的字符串不会被索引。对于字符串数组,ignore_above 将分别应用于每个数组元素,长度超过 ignore_above 的字符串元素将不被索引。
        • 默认为 256 字节
      • lower_case: 是否将分词进行小写转换,从而在匹配的时候实现忽略大小写
        • true: 转换小写
        • false:不转换小写
    • COMMENT 是可选的,用于指定注释

示例

CREATE TABLE table_name
(
  columns_difinition...,
  INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|unicode|chinese")] [COMMENT 'your comment']
  INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|unicode|chinese")] [COMMENT 'your comment']
  INDEX idx_name3(column_name3) USING INVERTED [PROPERTIES("parser" = "chinese", "parser_mode" = "fine_grained|coarse_grained")] [COMMENT 'your comment']
  INDEX idx_name4(column_name4) USING INVERTED [PROPERTIES("parser" = "english|unicode|chinese", "support_phrase" = "true|false")] [COMMENT 'your comment']
  INDEX idx_name5(column_name4) USING INVERTED [PROPERTIES("char_filter_type" = "char_replace", "char_filter_pattern" = "._"), "char_filter_replacement" = " "] [COMMENT 'your comment']
  INDEX idx_name5(column_name4) USING INVERTED [PROPERTIES("char_filter_type" = "char_replace", "char_filter_pattern" = "._")] [COMMENT 'your comment']
)
table_properties ... ;

倒排索引在不同数据模型中有不同的使用限制:

  • Aggregate 模型:只能为 Key 列建立倒排索引。
  • Unique 模型:需要开启 merge on write 特性,开启后,可以为任意列建立倒排索引。
  • Duplicate 模型:可以为任意列建立倒排索引
删除和查询语法
-- 删除
DROP INDEX idx_name ON table_name;
ALTER TABLE table_name DROP INDEX idx_name;
-- 取消创建
CANCEL BUILD INDEX ON TABLE_NAME;
CANCEL BUILD INDEX ON TABLE_NAME(job_id1,job_id2...);
-- 使用全文匹配
-- 1. 全文检索关键词匹配,通过MATCH_ANY MATCH_ALL完成
SELECT * FROM table_name WHERE column_name MATCH_ANY | MATCH_ALL 'keyword1 ...';

-- 1.1 logmsg中包含keyword1的行
SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword1';

-- 1.2 logmsg中包含keyword1或者keyword2的行,后面还可以添加多个keyword
SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword1 keyword2';

-- 1.3 logmsg中同时包含keyword1和keyword2的行,后面还可以添加多个keyword
SELECT * FROM table_name WHERE logmsg MATCH_ALL 'keyword1 keyword2';

-- 1.4 logmsg中同时包含keyword1和keyword2的行,并且按照keyword1在前,keyword2在后的顺序
SELECT * FROM table_name WHERE logmsg MATCH_PHRASE 'keyword1 keyword2';

-- 2. 普通等值、范围、IN、NOT IN,正常的SQL语句即可,例如
SELECT * FROM table_name WHERE id = 123;
SELECT * FROM table_name WHERE ts > '2023-01-01 00:00:00';
SELECT * FROM table_name WHERE op_type IN ('add', 'delete');
BloomFilter索引

BloomFilter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合,BloomFilter有以下特点:

  • 空间效率高的概率型数据结构,用来检查一个元素是否在一个集合中。
  • 对于一个元素检测是否存在的调用,BloomFilter会告诉调用者两个结果之一:可能存在或者一定不存在。
  • 缺点是存在误判,告诉你可能存在,不一定真实存在。

布隆过滤器实际上是由一个超长的二进制位数组和一系列的哈希函数组成。二进制位数组初始全部为0,当给定一个待查询的元素时,这个元素会被一系列哈希函数计算映射出一系列的值,所有的值在位数组的偏移量处置为1。

Doris中BloomFilter使用场景

满足以下几个条件时可以考虑对某列建立Bloom Filter 索引:

  1. 首先BloomFilter适用于非前缀过滤。
  2. 查询会根据该列高频过滤,而且查询条件大多是 in 和 = 过滤。
  3. 不同于Bitmap, BloomFilter适用于高基数列。比如UserID。因为如果创建在低基数的列上,比如 “性别” 列,则每个Block几乎都会包含所有取值,导致BloomFilter索引失去意义。
Doris中使用BloomFilter注意事项
  1. 不支持对Tinyint、Float、Double 类型的列建Bloom Filter索引。
  2. Bloom Filter索引只对 in 和 = 过滤查询有加速效果。
  3. 如果要查看某个查询是否命中了Bloom Filter索引,可以通过查询的Profile信息查看。
BloomFilte创建语法
-- PROPERTIES里加上"bloom_filter_columns"="k1,k2,k3"
CREATE TABLE IF NOT EXISTS sale_detail_bloom  (
    sale_date date NOT NULL COMMENT "销售时间",
    customer_id int NOT NULL COMMENT "客户编号",
    saler_id int NOT NULL COMMENT "销售员",
    sku_id int NOT NULL COMMENT "商品编号",
    category_id int NOT NULL COMMENT "商品分类",
    sale_count int NOT NULL COMMENT "销售数量",
    sale_price DECIMAL(12,2) NOT NULL COMMENT "单价",
    sale_amt DECIMAL(20,2)  COMMENT "销售总金额"
)
Duplicate  KEY(sale_date, customer_id,saler_id,sku_id,category_id)
PARTITION BY RANGE(sale_date)
(
PARTITION P_202111 VALUES [('2021-11-01'), ('2021-12-01'))
)
DISTRIBUTED BY HASH(saler_id) BUCKETS 10
PROPERTIES (
"replication_num" = "3",
"bloom_filter_columns"="saler_id,category_id",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "MONTH",
"dynamic_partition.time_zone" = "Asia/Shanghai",
"dynamic_partition.start" = "-2147483648",
"dynamic_partition.end" = "2",
"dynamic_partition.prefix" = "P_",
"dynamic_partition.replication_num" = "3",
"dynamic_partition.buckets" = "3"
);

-- 查看
 SHOW TABLE <TABLE_NAME>;
-- 删除
ALTER TABLE <DB_NAME.TABLE_NAME> SET ("bloom_filter_columns" = "");
-- 修改
ALTER TABLE <db.table_name> SET ("bloom_filter_columns" = "k1,k3");
NGram BloomFilter

Doris2.0新增的索引类型,为了提升like的查询性能

-- 创建语法
CREATE TABLE `table3` (
  `siteid` int(11) NULL DEFAULT "10" COMMENT "",
  `citycode` smallint(6) NULL COMMENT "",
  `username` varchar(32) NULL DEFAULT "" COMMENT "",
  INDEX idx_ngrambf (`username`) USING NGRAM_BF PROPERTIES("gram_size"="3", "bf_size"="256") COMMENT 'username ngram_bf index'
) ENGINE=OLAP
AGGREGATE KEY(`siteid`, `citycode`, `username`) COMMENT "OLAP"
DISTRIBUTED BY HASH(`siteid`) BUCKETS 10
PROPERTIES (
"replication_num" = "1"
);
-- PROPERTIES("gram_size"="3", "bf_size"="256"),分别表示gram的个数和bloom filter的字节数。
-- gram的个数跟实际查询场景相关,通常设置为大部分查询字符串的长度,bloom filter字节数,可以通过测试得出,通常越大过滤效果越好,可以从256开始进行验证测试看看效果。当然字节数越大也会带来索引存储、内存cost上升。
-- 如果数据基数比较高,字节数可以不用设置过大,如果基数不是很高,可以通过增加字节数来提升过滤效果。
-- 查看
SHOW INDEX FROM DB_NAME.TABLE_NAME;
-- 删除
ALTER TABLE DB_NAME.TABLE_NAME DROP INDEX INDEX_NAME;
-- 修改
ALTER TABLE DB_NAME.TABLE_NAME ADD INDEX INDEX_NAME(COL1,COL2...) USING NGRAM_BF PROPERTIES("gram_size"="256","bf_size"="512") COMMENT 'some commet'
NGram BloomFilter注意事项
  1. NGram BloomFilter只支持字符串列
  2. NGram BloomFilter索引和BloomFilter索引为互斥关系,即同一个列只能设置两者中的一个
  3. NGram大小和BloomFilter的字节数,可以根据实际情况调优,如果NGram比较小,可以适当增加BloomFilter大小
  4. 如果要查看某个查询是否命中了NGram Bloom Filter索引,可以通过查询的Profile信息查看
Bitmap索引

用户可以通过创建bitmap index 加速查询

语法
-- 创建
CREATE INDEX [IF NOT EXISTS] index_name ON table1 (col_name) USING BITMAP COMMENT 'balabala';
-- 查看
SHOW INDEX FROM db_name.table_name;
-- 删除
DROP INDEX [IF EXISTS] index_name ON db_name.table_name;
BitMap索引注意事项
  • bitmap 索引仅在单列上创建。
  • bitmap 索引能够应用在 DuplicateUniq 数据模型的所有列和 Aggregate模型的key列上。
  • bitmap 索引支持的数据类型如下:
    • TINYINT
    • SMALLINT
    • INT
    • BIGINT
    • CHAR
    • VARCHAR
    • DATE
    • DATETIME
    • LARGEINT
    • DECIMAL
    • BOOL
  • bitmap索引仅在 Segment V2 下生效。当创建 index 时,表的存储格式将默认转换为 V2 格式

你可能感兴趣的:(doris,doris)