Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Apache Doris

Apache Doris基础简介

作者:Aimyon_36

Apache Doris 是一个现代化的 MPP(Massively Parallel Processing,即大规模并行处理)分析型数据库产品,这篇文章主要介绍了Apache-Doris基础概念,需要的朋友可以参考下

Apache Doris 是一个现代化的 MPP(Massively Parallel Processing,即大规模并行处理)分析型数据库产品。仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析。

一、Doris架构

Doris 的架构很简洁,只设 FE(Frontend)、BE(Backend)两种角色、两个进程,不依赖于外部组件,方便部署和运维,FE、BE 都可线性扩展。

FE负责存储元数据和接收前端请求,BE负责执行查询,Observer负责扩展查询;
此处的Doris只是引用了Mysql的客户端协议,方便用户可以通过MysqlClient直接访问Doris,类似的mysql -user -password -P 9030就能通过像登录mysql客户端的方式访问Doris。

二、基本概念

1. Row & Column

一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。

从聚合模型的角度来说,Key 列相同的行,会聚合成一行。其中 Value 列的聚合方式由用户在表时指定。

2. Partition & Tablet

在 Doris 的存储引擎中,用户数据首先被划分成若干个分区(Partition),划分的规则通常是按照用户指定的分区列进行范围划分,比如按时间划分。而在每个分区内,数据被进一步的按照 Hash 的方式分桶,分桶的规则是要找用户指定的分桶列的值进行 Hash 后分桶。每个分桶就是一个数据分片(Tablet),也是数据划分的最小逻辑单元

此处的Partition & Tablet类似Hive中的分区分桶。

3. 建表示例

建表语法:

CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [database.]table_name
 (
 	column_definition1[, column_definition2, ...]
 	[, index_definition1[, index_definition12,]]
 )
 [ENGINE = [olap|mysql|broker|hive]]
 [key_desc]
 [COMMENT "table comment"];
 [partition_desc]
 [distribution_desc]
 [rollup_index]
 [PROPERTIES ("key"="value", ...)]
 [BROKER PROPERTIES ("key"="value", ...)];

Doris 的建表是一个同步命令,命令返回成功,即表示建表成功。
Doris 支持支持单分区和复合分区两种建表方式。

字段类型

TINYINT1 字节范围:-2^7 + 1 ~ 2^7 - 1
SMALLINT2 字节范围:-2^15 + 1 ~ 2^15 - 1
INT4 字节范围:-2^31 + 1 ~ 2^31 - 1
BIGINT8 字节范围:-2^63 + 1 ~ 2^63 - 1
LARGEINT16 字节范围:-2^127 + 1 ~ 2^127 - 1
FLOAT4 字节支持科学计数法
DOUBLE12 字节支持科学计数法
DECIMAL[(precision, scale)]16 字节保证精度的小数类型。 默认是DECIMAL(10, 0) precision: 1 ~ 27 scale: 0 ~ 9其中整数部分为 1 ~ 18不支持科学计数法
DATE3 字节范围:0000-01-01 ~ 9999-12-31
DATETIME8 字节范围:0000-01-01 00:00:00 ~ 9999-12-31 23:59:59
CHAR[(length)]定长字符串。长度范围:1 ~ 255。默认为 1
VARCHAR[(length)]变长字符串。长度范围:1 ~ 65533
BOOLEAN与 TINYINT 一样,0 代表 false,1 代表 true
HLL1~16385 个字节hll 列类型,不需要指定长度和默认值、长度根据数据的聚合程度系统内控制,并且 HLL 列只能通过 配 套 的 hll_union_agg 、Hll_cardinality、hll_hash 进行查询或使用
BITMAPbitmap 列类型,不需要指定长度和默认值。表示整型的集合,元素最大支持到 2^64 - 1
STRING变长字符串,0.15 版本支持,最大支持 2147483643 字节(2GB-4),长度还受 be 配置string_type_soft_limit, 实际能存储的最大长度取两者最小值。只能用在 value 列,不能用在 key 列和分区、分桶列

注:聚合模型在定义字段类型后,可以指定字段的 agg_type 聚合类型,如果不指定,则该列为 key 列。否则,该列为 value 列, 类型包括:SUM、MAX、MIN、REPLACE。

Range Partition

CREATE TABLE IF NOT EXISTS example_db.expamle_range_tbl
(
 `user_id` LARGEINT NOT NULL COMMENT "用户 id",
 `date` DATE NOT NULL COMMENT "数据灌入日期时间",
 `timestamp` DATETIME NOT NULL 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 "用户最小停留时间"
)
ENGINE=olap
AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`)
PARTITION BY RANGE(`date`)
(
 PARTITION `p201701` VALUES LESS THAN ("2017-02-01"),
 PARTITION `p201702` VALUES LESS THAN ("2017-03-01"),
 PARTITION `p201703` VALUES LESS THAN ("2017-04-01")
)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
PROPERTIES
(
 "replication_num" = "3",
 "storage_medium" = "SSD",
 "storage_cooldown_time" = "2018-01-01 12:00:00"
);

上述建表语句中,AGGREGATE KEY(user_id, date, timestamp, city, age, sex)属于是key列,下边的列在定义类型后添加了聚合类型(MAX,MIN,REPLACE,SUM)的列称为value列。

 `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 "用户最小停留时间"

此处还使用了复合分区,使用PARTITION BY RANGE(date)指定使用date字段进行范围分区,分区的范围是左闭右开的,并通过HASH算法根据user_id列进行分桶。

PARTITION BY RANGE(`date`)
(
 PARTITION `p201701` VALUES LESS THAN ("2017-02-01"),
 PARTITION `p201702` VALUES LESS THAN ("2017-03-01"),
 PARTITION `p201703` VALUES LESS THAN ("2017-04-01")
)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16

List Partition

CREATE TABLE IF NOT EXISTS example_db.expamle_list_tbl
(
 `user_id` LARGEINT NOT NULL COMMENT "用户 id",
 `date` DATE NOT NULL COMMENT "数据灌入日期时间",
 `timestamp` DATETIME NOT NULL 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 "用户最小停留时
间"
)
ENGINE=olap
AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`)
PARTITION BY LIST(`city`)
(
 PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"),
 PARTITION `p_usa` VALUES IN ("New York", "San Francisco"),
 PARTITION `p_jp` VALUES IN ("Tokyo")
)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
PROPERTIES
(
 "replication_num" = "3",
 "storage_medium" = "SSD",
 "storage_cooldown_time" = "2018-01-01 12:00:00"
);

(1)列的定义

在AGGREGATE KEY 数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为 Key 列。而其余则为 Value 列。

定义列时,可参照如下建议:

(2)分区分桶

Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。第二层是 Bucket(Tablet),仅支持 Hash 的划分方式。
也可以仅使用一层分区。使用一层分区时,只支持 Bucket 划分。
Partition

Range 分区 : 分区列通常为时间列,以方便的管理新旧数据。不可添加范围重叠的分区。

通过 VALUES […) 同时指定上下界比较容易理解。这里举例说明,当使用 VALUES LESS THAN (…) 语句进行分区的增删操作时,分区范围的变化情况:

(1)如上 expamle_range_tbl 示例,当建表完成后,会自动生成如下 3 个分区:

p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201703: [2017-03-01, 2017-04-01)

(2)增加一个分区 p201705 VALUES LESS THAN (“2017-06-01”),分区结果如下:

p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201703: [2017-03-01, 2017-04-01)
p201705: [2017-04-01, 2017-06-01)

(3)此时删除分区 p201703,则分区结果如下:

p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201705: [2017-04-01, 2017-06-01)

注意: p201702 和 p201705 的分区范围并没有发生变化,而这两个分区之间,出现了一个空洞:[2017-03-01, 2017-04-01)。即如果导入的数据范围在这个空洞范围内,是无法导入的。

List 分区:分区列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 数据类型,分区值为枚举值。只有当数据为目标分区枚举值其中之一时,才可以命中分区。不可添加范围重叠的分区。

Partition 支持通过 VALUES IN (…) 来指定每个分区包含的枚举值。下面通过示例说明,进行分区的增删操作时,分区的变化。

(1)如上 example_list_tbl 示例,当建表完成后,会自动生成如下 3 个分区:

p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")

(2)增加一个分区 p_uk VALUES IN (“London”),分区结果如下:

p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")
p_uk: ("London")

(3)删除分区 p_jp,分区结果如下:

p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_uk: ("London")

Bucket

指定的分桶列越多,分桶的粒度越细。
比如使用学科+班级的key列进行分桶,如果查询时只指定了班级,那么会发生全分区扫描。‘

复合分区使用场景:
(1)有时间维度或类似带有有序值的维度,可以以这类维度列作为分区列。分区粒度可以根据导入频次、分区数据量等进行评估。
(2)历史数据删除需求:如有删除历史数据的需求(比如仅保留最近 N 天的数据)。使用复合分区,可以通过删除历史分区来达到目的。也可以通过在指定分区内发送 DELETE 语句进行数据删除。
(3)解决数据倾斜问题:每个分区可以单独指定分桶数量。如按天分区,当每天的数据量差异很大时,可以通过指定分区的分桶数,合理划分不同分区的数据,分桶列建议选择区分度大的列。

(3)多列分区

Range 分区

PARTITION BY RANGE(`date`, `id`)
(
 PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"),
 PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"),
 PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01")
)

指定 date(DATE 类型) 和 id(INT 类型) 作为分区列。以上示例最终得到的分区如下:

p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") )
p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE))

注意,最后一个分区用户缺省只指定了 date 列的分区值,所以 id 列的分区值会默认填充 MIN_VALUE。当用户插入数据时,分区列值会按照顺序依次比较,最终得到对应的分区。举例如下:

数据 --> 分区
2017-01-01, 200 --> p201701_1000
2017-01-01, 2000 --> p201701_1000
2017-02-01, 100 --> p201701_1000
2017-02-01, 2000 --> p201702_2000
2017-02-15, 5000 --> p201702_2000
2017-03-01, 2000 --> p201703_all
2017-03-10, 1 --> p201703_all
2017-04-01, 1000 --> 无法导入
2017-05-01, 1000 --> 无法导入

List 分区

PARTITION BY LIST(`id`, `city`)
(
 PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", 
"Shanghai")),
 PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", 
"Shanghai")),
 PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", 
"Shanghai"))
)

指定 id(INT 类型) 和 city(VARCHAR 类型) 作为分区列。最终得到的分区如下:

p1_city: [("1", "Beijing"), ("1", "Shanghai")]
p2_city: [("2", "Beijing"), ("2", "Shanghai")]
p3_city: [("3", "Beijing"), ("3", "Shanghai")]

当用户插入数据时,分区列值会按照顺序依次比较,最终得到对应的分区。举例如下:

数据 ---> 分区
1, Beijing ---> p1_city
1, Shanghai ---> p1_city
2, Shanghai ---> p2_city
3, Beijing ---> p3_city
1, Tianjin ---> 无法导入
4, Beijing ---> 无法导入

(4)PROPERTIES

(5)ENGINE

ENGINE 的类型是 olap,即默认的 ENGINE 类型。在 Doris 中,只有这个ENGINE 类型是由 Doris 负责数据管理和存储的。其他 ENGINE 类型,如 mysql、broker、es 等等,本质上只是对外部其他数据库或系统中的表的映射,以保证 Doris 可以读取这些数据。而 Doris 本身并不创建、管理和存储任何非 olap ENGINE 类型的表和数据。

三、数据模型

Doris 的数据模型主要分为 3 类:Aggregate、Uniq、Duplicate

1. Aggregate 模型

表中的列按照是否设置了 AggregationType,分为 Key(维度列)和 Value(指标列)。没有设置 AggregationType 的称为 Key,设置了 AggregationType 的称为 Value。

当导入数据时,对于 Key 列相同的行会聚合成一行, Value 列会按照设置的AggregationType 进行聚合。AggregationType 目前有以下四种聚合方式:

数据的聚合,在 Doris 中有如下三个阶段发生:
(1)每一批次数据导入的 ETL 阶段。该阶段会在每一批次导入的数据内部进行聚合。
(2)底层 BE 进行数据 Compaction 的阶段。该阶段,BE 会对已导入的不同批次的数据进行进一步的聚合。
(3)数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合。数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。

举个栗子:导入数据聚合

CREATE TABLE IF NOT EXISTS test_db.example_site_visit
(
 `user_id` LARGEINT NOT NULL COMMENT "用户 id",
 `date` DATE NOT NULL COMMENT "数据灌入日期时间",
 `city` VARCHAR(20) COMMENT "用户所在城市",
 `age` SMALLINT COMMENT "用户年龄",
 `sex` TINYINT COMMENT "用户性别",
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 
00:00:00" COMMENT "用户最后一次访问时间",
 `last_visit_date_not_null` DATETIME REPLACE_IF_NOT_NULL 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`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;

插入数据:

执行结果:

聚合发生在所有key列的值都一样的情况下,类似group by c1,c2,c3

Insert into 单条数据这种操作在 Doris 里不能在生产使用,会引发写阻塞,因为每一批次的数据导入会引发数据的聚合逻辑操作。

再举个栗子: 保留明细数据

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;

使用更细粒度的列从而让Key列的值都不一样,达到保存明细数据的效果,上述使用timestamp这种细粒度的列值,能够记录到每条数据。

2. Uniq 模型

在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性,即如何获得 Primary Key 唯一性约束。因此,我们引入了 Uniq 的数据模型。该模型本质上是聚合模型的一个特例,也是一种简化的表结构表示方式。

建表语句:

CREATE TABLE IF NOT EXISTS test_db.user
(
 `user_id` LARGEINT NOT NULL COMMENT "用户 id",
 `username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
 `city` VARCHAR(20) COMMENT "用户所在城市",
 `age` SMALLINT COMMENT "用户年龄",
 `sex` TINYINT COMMENT "用户性别",
 `phone` LARGEINT COMMENT "用户电话",
 `address` VARCHAR(500) COMMENT "用户地址",
 `register_time` DATETIME COMMENT "用户注册时间"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;

插入数据:

insert into test_db.user values\
(10000,'wuyanzu',' 北 京 ',18,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\
(10000,'wuyanzu',' 北 京 ',19,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\
(10000,'zhangsan','北京',20,0,12345678910,'北京海淀区','2017-11-15 06:10:20');

执行结果:

Unique模型确保了UNIQUE KEY(user_id, username)的唯一行,而其他的列会选择最新的列值。

3. Duplicate 模型

在某些多维分析场景下,数据既没有主键,也没有聚合需求。Duplicate 数据模型可以满足这类需求。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。 而在建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序

4. 模型选建议

因为数据模型在建表时就已经确定,且无法修改。所以,选择一个合适的数据模型非常重要。

(1)Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性

(2)Uniq 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。

(3)Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不 受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)

如何应对业务上的Count(*)场景问题:
Doris提供的预聚合导致用户在查询时,获取的结果都是对key聚合后的结果,无法获取到相应key列的条数这样的结果。

因此,当业务上有频繁的 count( * ) 查询时,增加一个 count 列,并且导入数据中,该列值恒为 1。则 select count() from table; 的结果等价于 select sum(count) from table;。而后者的查询效率将远高于前者。不过这种方式也有使用限制,就是用户需要自行保证,不会重复导入AGGREGATE KEY 列都相同的行。否则,select sum(count) from table; 只能表述原始导入的行数,而不是 select count() from table; 的语义。

另一种方式,就是 将如上的 count 列的聚合类型改为 REPLACE,且依然值恒为 1。那
么 select sum(count) from table; 和 select count(*) from table; 的结果将是一致的。并且这种
方式,没有导入重复行的限制。

四、ROLLUP

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

1. Aggregate模型的ROLLUP

因为 Uniq 只是 Aggregate 模型的一个特例,所以不加以区别。

查看表的结构信息

例如想要查看每个用户的总消费,建立一个只有 user_id 和 cost 的 rollup。alter table example_site_visit2 add rollup rollup_cost_userid(user_id,cost);

再次查看表结构

查看你ROLLUP表的完成状态。
SHOW ALTER TABLE ROLLUP;

Aggregate模型的roolup表完成了对基表更粗粒度的聚合,具体的,当数据插入到基表中时,Doris会自动将数据按照指定的聚合键进行分组,并计算每个分组的聚合结果,roolup表也是如此完成数据的预聚合。

2. Duplicate 模型中的 ROLLUP

因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序,以命中前缀索引的作用。

前缀索引

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

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

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

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

以下表结构的前缀索引为 user_id(8 Bytes) + age(4 Bytes) + message(prefix 20 Bytes)
[message列的一部分-取20个字节]。

ColumnNameColumnType
user_idBIGINT
ageINT
messageVARCHAR(100)
max_dwell_timeDATETIME
min_dwell_timeDATETIME

以下表结构的前缀索引为 user_name(20 Bytes)。即使没有达到 36 个字节,因为遇到VARCHAR,所以直接截断,不再往后继续。

ColumnNameColumnType
user_nameVARCHAR(20)
ageINT
messageVARCHAR(100)
max_dwell_timeDATETIME
min_dwell_timeDATETIME

当我们的查询条件,是前缀索引的前缀时,可以极大的加快查询速度。比如在第一个例子中,我们执行如下查询:

SELECT * FROM table WHERE user_id=1829239 and age=20;

该查询的效率会远高于如下查询:

SELECT * FROM table WHERE age=20;

所以在建表时,正确的选择列顺序,能够极大地提高查询效率。

ROLLUP 调整前缀索引

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

Base 表结构如下:

ColumnNameColumnType
user_idBIGINT
ageINT
messageVARCHAR(100)
max_dwell_timeDATETIME
min_dwell_timeDATETIME

我们可以在此基础上创建一个 ROLLUP 表:

ColumnNameColumnType
ageINT
user_idBIGINT
messageVARCHAR(100)
max_dwell_timeDATETIME
min_dwell_timeDATETIME

可以看到,ROLLUP 和 Base 表的列完全一样,只是将 user_id 和 age 的顺序调换了。那么当我们进行如下查询时:

SELECT * FROM table where age=20 and message LIKE "%error%";

会优先选择 ROLLUP 表,因为 ROLLUP 的前缀索引匹配度更高。

3. DROLLUP 的使用说明

五、物化视图

物化视图就是包含了查询结果的数据库对象,可能是对远程数据的本地 copy,也可能是一个表或多表 join 后结果的行或列的子集,也可能是聚合后的结果。说白了,就是预先存储查询结果的一种数据库对象。

在 Doris 中的物化视图,就是查询结果预先存储起来的特殊的表。

物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。

视图是一种虚拟表,它是基于一个或多个数据库表的查询结果构建而成的。视图并不实际存储数据,而是根据定义的查询逻辑,在查询时动态生成结果,可以理解为视图是定义好的一组Sql语句,用户查询视图时,数据库会根据视图的定义执行相应的查询操作,并返回结果。

物化视图,是一种将查询结果预先存储起来的特殊的表。当基表数据更新时,物化视图也会一同更新。

物化视图创建删除语法:

create materialized view 物化视图名 as {sql语句}
DROP MATERIALIZED VIEW 物化视图名 on Base 表名;

适用场景:

优势:

物化视图 VS Rollup:

物化视图不仅达到预聚合的目的,也能像rollup一样修改字段顺序,优化前缀索引。

Doris 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和PostgreSQL, Oracle 都是一致的。但是 Doris 目前创建物化视图只能在单表操作,不支持 join

1. 创建物化视图

首先要根据查询语句的特点来决定创建一个什么样的物化视图。并不是说物化视图定义和某个查询语句一模一样就最好。这里有两个原则:

(1)从查询语句中抽象出,多个查询共有的分组和聚合方式作为物化视图的定义。
(2)不需要给所有维度组合都创建物化视图。

首先第一个点,一个物化视图如果抽象出来,并且多个查询都可以匹配到这张物化视图。这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。如果物化视图只和某个特殊的查询很贴合,而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。

第二点就是,在实际的分析查询中,并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可,从而到达一个空间和时间上的平衡。

创建物化视图是一个异步的操作,也就是说用户成功提交创建任务后,Doris 会在后台对存量的数据进行计算,直到创建成功。在构建期间,用户依然可以正常的查询和导入新的数据。创建任务会自动处理当前的存量数据和所有新到达的增量数据,从而保持和 base 表的数据一致性。

2. 物化视图的删选匹配

销售记录表:Base表

FieldTypeKeyExtra
record_idINTTRUE
saller_idINTTRUE
store_idINTTRUE
sale_dateDATEFALSE
sale_amtBIGINTFALSE

mv_1: 存储了不同时间不同销售员的售卖量

FieldTypeKeyExtra
saller_idINTTRUE
sale_dateDATEFALSE
sale_amtBIGINTFALSEsum

mv_2:存储了不同时间不同门店的销售量

FieldTypeKeyExtra
store_idINTTRUE
sale_dateDATEFALSE
sale_amtBIGINTFALSEsum

mv_3:存储了每个销售员的总销售量

FieldTypeKeyExtra
saller_idINTTRUE
sale_amtBIGINTFALSEsum

当查询 7 月 19 日,各个销售员都买了多少钱的话。就可以匹配 mv_1 物化视图。直接对 mv_1 的数据进行查询。

物化视图的自动匹配分为下面两个步骤:

(1)根据查询条件删选出一个最优的物化视图:这一步的输入是所有候选物化视图表的元数据,根据查询的条件从候选集中输出最优的一个物化视图

这里Base表和mv_1表均符合。

简而言之,物化视图的筛选过程,就是按照sql关键字的执行顺序通过查询字段和物化视图的匹配程度逐一删选出最符合的物化视图。

候选集过滤完后输出一个集合,这个集合中的所有表都能满足查询的需求。但每张表的查询效率都不同。这时候就需要再这个集合根据前缀索引是否能匹配到,以及聚合程度的高低来选出一个最优的物化视图。

从表结构中可以看出,base 表的销售日期列是一个非排序列,而物化视图表的日期是一个排序列,同时聚合程度上 mv_1 表明显比 base 表高。所以最后选择出 mv_1 作为该查询的最优匹配。

(2)根据选出的物化视图对查询进行改写:这一步是结合上一步选择出的最优物化视图,进行查询的改写,最终达到直接查询物化视图的目的。

查询改写的原则:

其中 bitmap 和 hll 的聚合函数在查询匹配到物化视图后,查询的聚合算子会根据物化视图的表结构进行一个改写。

3. 物化视图的限制

六、修改和删除语句

1. 修改表

使用 ALTER TABLE 命令可以对表进行修改,包括 partition 、rollup、schema change、rename 和 index 五种。

rename

将名为 table1 的表修改为 table2

ALTER TABLE table1 RENAME table2;

将表 example_table 中名为 rollup1 的 rollup index 修改为 rollup2

ALTER TABLE example_table RENAME ROLLUP rollup1 rollup2;

将表 example_table 中名为 p1 的 partition 修改为 p2

ALTER TABLE example_table RENAME PARTITION p1 p2;

partition

增加分区, 使用默认分桶方式
现有分区 [MIN, 2013-01-01),增加分区 [2013-01-01, 2014-01-01),

ALTER TABLE example_db.my_table
ADD PARTITION p1 VALUES LESS THAN ("2014-01-01");

增加分区,使用新的分桶数

ALTER TABLE example_db.my_table
ADD PARTITION p1 VALUES LESS THAN ("2015-01-01")
DISTRIBUTED BY HASH(k1) BUCKETS 20;

增加分区,使用新的副本数

ALTER TABLE example_db.my_table
ADD PARTITION p1 VALUES LESS THAN ("2015-01-01")
("replication_num"="1");

修改分区副本数

ALTER TABLE example_db.my_table
MODIFY PARTITION p1 SET("replication_num"="1");

批量修改指定分区

ALTER TABLE example_db.my_table
MODIFY PARTITION (*) SET("storage_medium"="HDD");

批量修改所有分区

ALTER TABLE example_db.my_table
MODIFY PARTITION (*) SET("storage_medium"="HDD");

删除分区

ALTER TABLE example_db.my_table
DROP PARTITION p1;

增加一个指定上下界的分区

ALTER TABLE example_db.my_table
ADD PARTITION p1 VALUES [("2014-01-01"), ("2014-02-01"));

rollup

创建 index: example_rollup_index,基于 base index(k1,k2,k3,v1,v2)。列式存储。

ALTER TABLE example_db.my_table
ADD ROLLUP example_rollup_index(k1, k3, v1, v2);

创建 index: example_rollup_index2,基于 example_rollup_index(k1,k3,v1,v2)

ALTER TABLE example_db.my_table
ADD ROLLUP example_rollup_index2 (k1, v1)
FROM example_rollup_index;

创建 index: example_rollup_index3, 基于 base index (k1,k2,k3,v1), 自定义 rollup 超时时间一小时。

ALTER TABLE example_db.my_table
ADD ROLLUP example_rollup_index(k1, k3, v1)
PROPERTIES("timeout" = "3600");

删除 index: example_rollup_index2

ALTER TABLE example_db.my_table
DROP ROLLUP example_rollup_index2;

2. 删除数据

Doris 目前可以通过两种方式删除数据:DELETE FROM 语句和 ALTER TABLE DROP PARTITION 语句。

DELETE FROM Statement(条件删除)

DELETE FROM table_name [PARTITION partition_name]
WHERE
column_name1 op { value | value_list } [ AND column_name2 op { value 
| value_list } ...];
delete from student_kafka where id=1;

DROP PARTITION Statement(删除分区)
该命令可以直接删除指定的分区。因为 Partition 是逻辑上最小的数据管理单元,所以使用 DROP PARTITION 命令可以很轻量的完成数据删除工作。并且该命令不受 load 以及任何其他操作的限制,同时不会影响查询效率。是比较推荐的一种数据删除方式。

该命令是同步命令,执行成功即生效。而后台数据真正删除的时间可能会延迟 10 分钟左右。

七、数据的导入导出

导入(Load)功能就是将用户的原始数据导入到 Doris 中。导入成功后,用户即可通过Mysql 客户端查询数据。

为适配不同的数据导入需求,Doris 系统提供了 6 种不同的导入方式。每种导入方式支持不同的数据源,存在不同的使用方式(异步,同步)。

所有导入方式都支持 csv 数据格式。其中 Broker load 还支持 parquet 和 orc 数据格式。

通过 Broker 进程访问并读取外部数据源(如 HDFS)导入到 Doris。用户通过 Mysql协议提交导入作业后,异步执行。通过 SHOW LOAD 命令查看导入结果。

用户通过 HTTP 协议提交请求并携带原始数据创建导入。主要用于快速将本地文件或数据流中的数据导入到 Doris。导入命令同步返回导入结果。

类似 MySQL 中的 Insert 语句,Doris 提供 INSERT INTO tbl SELECT …; 的方式从Doris 的表中读取数据并导入到另一张表。或者通过 INSERT INTO tbl VALUES(…); 插入单条数据。

用户通过 HTTP 协议提交多个导入作业。Multi Load 可以保证多个导入作业的原子生效。

Routine load

通过 S3 协议直接导入

本文不再赘述数据导入导出,具体的可前往官网查看。

前往Apache-Doris

到此这篇关于Apache-Doris基础概念的文章就介绍到这了,更多相关Apache Doris内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文