Skip to content

Doris索引介绍

Apache Doris 存储引擎采用类似 LSM 树的结构提供快速的数据写入支持。进行数据导入时,数据会先写入 Tablet 对应的 MemTable 中,当 MemTable 写满之后,会将 MemTable 里的数据刷写(Flush)到磁盘,生成一个个不超过 256MB 的不可变的 Segment 文件。

MemTable 采用 SkipList 的数据结构,将数据暂时保存在内存中,SkipList 会按照 Key 对数据行进行排序,因此,刷写到磁盘上的 Segment 文件也是按 Key 排序的。Apache Doris 底层采用列存的方式来存储数据,每一列数据会被分为多个 Data Page。

为了提高数据读取效率,Apache Doris 底层存储引擎提供了丰富的索引类型,目前 Doris 主要支持两类索引:

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

数据从 MemTable 刷写到磁盘的过程分为两个阶段:

  • 第一阶段是将 MemTable 中的行存结构在内存中转换为列存结构,并为每一列生成对应的索引结构;

  • 第二阶段是将转换后的列存结构写入磁盘,生成 Segment 文件。

查看索引

展示指定 table_name 的下索引

sql
SHOW INDEX FROM example_db.table_name;

删除索引

删除指定 table_name 的下索引

sql
DROP INDEX [IF EXISTS] index_name ON [db_name.]table_name;

唯一索引

创建索引

Doris支持多种类型的索引,包括主键索引、唯一索引、普通索引等。在创建表时,可以同时创建索引。例如,创建一个包含主键索引的表:

sql
CREATE TABLE user (
  id INT,
  name VARCHAR(20),
  PRIMARY KEY(id)
);

也可以在创建表后,通过ALTER TABLE语句来添加索引。例如,添加一个唯一索引:

sql
ALTER TABLE user ADD UNIQUE INDEX uk_name(name);

BloomFilter索引

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

  • 空间效率高的概率型数据结构,用来检查一个元素是否在一个集合中。

  • 对于一个元素检测是否存在的调用,BloomFilter会告诉调用者两个结果之一:可能存在或者一定不存在。

  • 缺点是存在误判,告诉你可能存在,不一定真实存在。

Bloom_filter.svg

创建索引

建表时创建BloomFilter索引

sql
create table ...
DISTRIBUTED BY HASH(saler_id) BUCKETS 10
PROPERTIES (
"bloom_filter_columns"="saler_id,category_id"
);

查看BloomFilter索引 查看我们在表上建立的BloomFilter索引是使用:

sql
SHOW CREATE TABLE <table_name>;

删除BloomFilter索引 删除索引即为将索引列从bloom_filter_columns属性中移除:

sql
ALTER TABLE <db.table_name> SET ("bloom_filter_columns" = "");

修改BloomFilter索引 修改索引即为修改表的bloom_filter_columns属性:

sql
ALTER TABLE <db.table_name> SET ("bloom_filter_columns" = "k1,k3");

使用场景和注意事项

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信息查看。

前缀索引

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

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

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

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

前缀索引是一种稀疏索引。数据刷写过程中,每写入一定的数据行(默认为 1024 行)就会生成一条前缀索引项。前缀索引会对每一个索引间隔的第一个数据行的前缀字段进行编码,前缀字段的编码与前缀字段的值具有相同的排序规则,即前缀字段的值排序越靠前,对应的编码值排序也越靠前。Segment 文件是按 Key 排序的,因此,前缀索引项也是按 Key 排序的。

一个 Segment 文件中的前缀索引数据保存在一个独立的 Short Key Page 中,其中包含每一条前缀索引项的编码数据、每一条前缀索引项的 offset、Short Key Page 的 footer 以及 Short Key Page 的 Checksum 信息。Short Key Page 的 footer 中记录了 Page 的类型、前缀索引编码数据的大小、前缀索引 offset 数据的大小、前缀索引项的数目等信息。

Short Key Page 在 Segment 中的 offset 和大小会被保存在Segment文件的footer中,以便于数据读取时能够正确地从Segment文件中加载出前缀索引数据。前缀索引的存储结构如图所示。

倒排索引

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

inverted index:倒排索引,是信息检索领域常用的索引技术,将文本分割成一个个词,构建 词 -> 文档编号 的索引,可以快速查找一个词在哪些文档出现。

Doris使用CLucene作为底层的倒排索引库。CLucene是一个用C++实现的高性能、稳定的Lucene倒排索引库。Doris进一步优化了CLucene,使得它更简单、更快、更适合数据库场景。

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

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

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

  • 增加了字符串类型的全文检索
    • 支持字符串全文检索,包括同时匹配多个关键字MATCH_ALL、匹配任意一个关键字MATCH_ANY
    • 支持字符串数组类型的全文检索
    • 支持英文、中文分词
  • 加速普通等值、范围查询,覆盖bitmap索引的功能,未来会代替bitmap索引
    • 支持字符串、数值、日期时间类型的 =, !=, >, >=, <, <= 快速过滤
    • 支持字符串、数字、日期时间数组类型的 =, !=, >, >=, <, <=
  • 支持完善的逻辑组合
    • 新增索引对OR NOT逻辑的下推
    • 支持多个条件的任意AND OR NOT组合
  • 灵活、快速的索引管理
    • 支持在创建表上定义倒排索引
    • 支持在已有的表上增加倒排索引,而且支持增量构建倒排索引,无需重写表中的已有数据
    • 支持删除已有表上的倒排索引,无需重写表中的已有数据

建表时定义倒排索引:

sql
CREATE TABLE table_name
(
  columns_difinition,
  INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment']
  INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment']
)
table_properties;
  • USING INVERTED 是必须的,用于指定索引类型是倒排索引
  • PROPERTIES 是可选的,用于指定倒排索引的额外属性,目前有一个属性parser指定分词器
    • 默认不指定代表不分词
    • english是英文分词,适合被索引列是英文的情况,用空格和标点符号分词,性能高
    • chinese是中文分词,适合被索引列有中文或者中英文混合的情况,采用jieba分词库,性能比english分词低
  • COMMENT 是可选的,用于指定注释
sql
CREATE TABLE table_name
(
    columns_difinition,
    INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment']
    INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment']
)
table_properties;

已有表增加倒排索引:

sql
-- 语法1
CREATE INDEX idx_name ON table_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'];
-- 语法2
ALTER TABLE table_name ADD INDEX idx_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'];

删除倒排索引:

sql
-- 语法1
DROP INDEX idx_name ON table_name;
-- 语法2
ALTER TABLE table_name DROP INDEX idx_name;

利用倒排索引加速查询:

sql
-- 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 'keyword2 keyword2';

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