Doris学习

doris简介

由百度大数据部研发

之前叫百度palo,2018年共享到apache社区,改名doris

MPP分析型数据库产品

亚秒级响应
架构非常简洁,易于运维
支持10pb以上的超大数据集

满足多种数据分析需求

固定历史报表
实时数据分析
交互式数据分析
探索式数据分析
流批一体架构

场景图

doris架构

架构图
doris架构设计简单,只设有FE(Frontend)、BE(Backend)两种角色、两个进程,不依赖于外部组件,方便部署和运维,FE、BE都可线形拓展。

FE(Frontend):

存储、维护集群元数据;负责接收、解析查询请求,规划查询计划,调度查询执行,返回查询结果。三种角色:

(1)Leader和Follower:主要是用来达到元数据的高可用,保证单节点宕机的情况下,元数据能够实时的在线回复,而不影响整个服务。
(2)Observer:用来扩展查询节点,同时起到元数据备份的作用。如果在发现集群压力非常大的情况下,需要扩展整个查询的能力,那么可以加observer的节点。observer不参与任何的写入,只参与读取。

BE(Backend):

负责物理数据的存储和计算;依据FE生成的物理计划,分布式的执行查询。

数据的可靠性由BE保证,BE会对整个数据存储多副本或者三副本。副本数可根据需求动态调整。

MYSQL Client

Doris借助Mysql协议,用户使用任意MySQL的ODBC/JDBC以及MySQL的酷户端,可以直接访问Doris。

Broker

Broker为一个独立的无状态进程。封装了文件系统接口,提供Doris读取远端存储系统中文件的能力,例如HDFS,S3

数据表的创建

创建用户和数据库

创建test用户

mysql -h -u -p -P9030
create user 'test' identified by 'test'

创建数据库

create database test_db;

用户授权

grant all on test_db to test;

基本概念

在doris中,数据都以关系表(Table) 的形式进行逻辑上的描述。

Row&Column

一张表包含Row和Column。Row是用户一行数据。Column用于描述一行数据中不同的字段。

1)在默认的数据模型中,Column只分为排序列和非排序列。存储引擎会按照排序列对数据进行排序存储,并建立稀疏索引,以便在排序数据上进行快速查找。
2)而在聚合模型中,Column可以分为两大类:Key和Value。从业务角度看,Key和Value可以分别对应维度列和指标列。从聚合模型角度来说,key列相同的行聚合成一行。其中Value列的聚合方式由用户在建表时指定。

Partition&Tablet

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

1)Tablet 之间的数据是没有交集的,独立存储的。Tablet 也是数据移动、复制等操作 的最小物理存储单元。 
2)Partition 可以视为是逻辑上最小的管理单元。数据的导入与删除,都可以或仅能针 对一个 Partition 进行。

建表示例

建表语法

使用 CREATE TABLE 命令建立一个表(Table)。更多详细参数可以查看:

HELP CREATE TABLE;

建表语法:

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 支持支持单分区和复合分区两种建表方式。
1) 复合分区:既有分区也有分桶

第一级称为 Partition,即分区。用户可以指定某一维度列作为分区列(当前只支持整型和时间类型的列),并指定每个分区的取值范围。
第二级称为 Distribution,即分桶。用户可以指定一个或多个维度列以及桶数对数据进行 HASH 分布。
  1. 单分区:只做HASH分布,只分桶

字段类型

字段名内存范围
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。

建表示例

我们以一个建表操作来说明Doris的数据划分。

Ranger 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"
);
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"
);

数据划分

以上述表来理解

列定义

以 AGGREGATE KEY 数据模型为例进行说明。更多数据模型参阅 Doris 数据模型。
列的基本类型,可以通过在 mysql-client 中执行 HELP CREATE TABLE; 查看。
AGGREGATE KEY 数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为 Key 列。而其余则为 Value 列。
定义列时,可参照如下建议:

  1. Key 列必须在所有 Value 列之前。
  2. 尽量选择整型类型。因为整型类型的计算和查找比较效率远高于字符串。
  3. 对于不同长度的整型类型的选择原则,遵循够用即可。
  4. 对于 VARCHAR 和 STRING 类型的长度,遵循 够用即可。
  5. 所有列的总字节长度(包括 Key 和 Value)不能超过 100KB。

分区和分桶

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

Partition
  1. Partition 列可以指定一列或多列。分区类必须为 KEY 列。
  2. 不论分区列是什么类型,在写分区值时,都需要加双引号。
  3. 分区数量理论上没有上限。
  4. 当不使用 Partition 建表时,系统会自动生成一个和表名同名的,全值围的Partition。该 Partition 对用户不可见,并且不可删改。
Range 分区

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

  • VALUES LESS THAN (…) 仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区间。分区的删除不会改变已存在分区的范围。删除分区可能出现空洞。
  • VALUES […) 指定同时指定上下界,生成一个左闭右开的区间。

当range分区进行增删操作时,容易出现空洞,会导致无法导入

List分区

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

Bucket
  1. 如果使用了 Partition,则 DISTRIBUTED … 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。
  2. 分桶列可以是多列,但必须为 Key 列。分桶列可以和 Partition 列相同或不同。
  3. 分桶列的选择,是在查询吞吐和查询并发之间的一种权衡:
  • 如果选择多个分桶列,则数据分布更均匀。
    如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。
  • 如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。
    此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的 IO 影响较小(尤其当不同桶分布在不同磁盘时),所以这种方式适合高并发的点查询场景。
  1. 分桶的数量理论上没有上限。
使用复合分区的场景

以下场景推荐使用复合分区:

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

Doris 支持指定多列作为分区列,示例如下:

  1. 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")
)

最后一个分区没有设置id列,系统默认会补充一个MIN_VALUE

  1. 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"))
)

Properties

在建表语句的最后 PROPERTIES 中,可以指定以下两个参数:

replication_num

每个 Tablet 的副本数量。默认为 3,建议保持默认即可。在建表语句中所有 Partition中的 Tablet 副本数量统一指定。而在增加新分区时,可以单独指定新分区中 Tablet 的副本数量。

副本数量可以在运行时修改。强烈建议保持奇数。

最大副本数量取决于集群中独立 IP 的数量(注意不是 BE 数量)。Doris 中副本分布的原则是,不允许同一个 Tablet 的副本分布在同一台物理机上,而识别物理机即通过 IP。所以,即使在同一台物理机上部署了 3 个或更多 BE 实例,如果这些 BE 的 IP 相同,则依然只能设置副本数为 1。

对于一些小,并且更新不频繁的维度表,可以考虑设置更多的副本数。这样在 Join 查询时,可以有更大的概率进行本地数据 Join。

storage_medium & storage_cooldown_time

BE 的数据存储目录可以显式的指定为 SSD 或者 HDD(通过 .SSD 或者 .HDD 后缀区分)。建表时,可以统一指定所有 Partition 初始存储的介质。注意,后缀作用是显式指定磁盘介质,而不会检查是否与实际介质类型相符。

默认初始存储介质可通过 fe 的配置文件 fe.conf 中指定 default_storage_medium=xxx,如果没有指定,则默认为 HDD。如果指定为 SSD,则数据初始存放在 SSD 上。

如果没有指定storage_cooldown_time,则默认 30 天后,数据会从 SSD 自动迁移到 HDD上。如果指定了 storage_cooldown_time,则在到达 storage_cooldown_time 时间后,数据才会迁移。

注意,当指定storage_medium时,如果FE参数enable_strict_storage_medium_check 为False 该参数只是一个“尽力而为”的设置。即使集群内没有设置SSD存储介质,也不会报错,而是自动存储在可用的数据目录中。 同样,如果SSD介质不可访问、空间不足,都可能导致数据初始直接存储在其他可用介质上。而数据到期迁移到 HDD 时,如果 HDD 介质不可访问 、 空间不足 ,也可能迁移失败(但是会不断尝试) 。 如果FE参数enable_strict_storage_medium_check 为 True 则当集群内没有设置 SSD 存储介质时,会报错Failed to find enough host in all backends with storage medium is SSD。

ENGINE

ENGIN默认是OLAP,只有OLAP是Doris负责管理数据和存储。

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

数据模型

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

Aggregate模型

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

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

  • SUM:求和,多行的 Value 进行累加。
  • REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
    REPLACE_IF_NOT_NULL :当遇到 null 值则不更新。
  • MAX:保留最大值。
  • MIN:保留最小值。

数据聚合会发生在三个时间段

  • 每一批次数据导入的 ETL 阶段。该阶段会在每一批次导入的数据内部进行聚合。
  • 底层 BE 进行数据 Compaction 的阶段。该阶段,BE 会对已导入的不同批次的数据进行进一步的聚合。
  • 数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合。
数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。
示例一
建表
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;
插入数据
insert into test_db.example_site_visit values\
(10000,'2017-10-01','北京',20,0,'2017-10-01 06:00:00','2017-10-01
06:00:00',20,10,10),\
(10000,'2017-10-01','北京',20,0,'2017-10-01 07:00:00','2017-10-01
07:00:00',15,2,2),\
(10001,'2017-10-01','北京',30,1,'2017-10-01 17:05:45','2017-10-01
07:00:00',2,22,22),\
(10002,'2017-10-02',' 上 海 ',20,1,'2017-10-02
12:59:12',null,200,5,5),\
(10003,'2017-10-02','广州',32,0,'2017-10-02 11:20:00','2017-10-02
11:20:00',30,11,11),\
(10004,'2017-10-01','深圳',35,0,'2017-10-01 10:00:15','2017-10-01
10:00:15',100,3,3),\
(10004,'2017-10-03','深圳',35,0,'2017-10-03 10:20:22','2017-10-0310:20:22',11,6,6);

Insert into 单条数据这种操作在 Doris 里只能演示不能在生产使用,会引发写阻塞。

查看表
select * from test_db.example_site_visit;
示例二
建表
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;
插入数据
insert into test_db.example_site_visit2 values(10000,'2017-10-
01','2017-10-01 08:00:05',' 北 京 ',20,0,'2017-10-01
06:00:00',20,10,10),\
(10000,'2017-10-01','2017-10-01 09:00:05','北京',20,0,'2017-10-01
07:00:00',15,2,2),\
(10001,'2017-10-01','2017-10-01 18:12:10','北京',30,1,'2017-10-01
17:05:45',2,22,22),\
(10002,'2017-10-02','2017-10-02 13:10:00','上海',20,1,'2017-10-02
12:59:12',200,5,5),\
(10003,'2017-10-02','2017-10-02 13:15:00','广州',32,0,'2017-10-02
11:20:00',30,11,11),\
(10004,'2017-10-01','2017-10-01 12:12:48','深圳',35,0,'2017-10-01
10:00:15',100,3,3),\
(10004,'2017-10-03','2017-10-03 12:38:20','深圳',35,0,'2017-10-03
10:20:22',11,6,6);
查看表
select * from test_db.example_site_visit2;
示例三
往实例一中继续插入数据
insert into test_db.example_site_visit values(10004,'2017-10-03','深圳',35,0,'2017-10-03 11:22:00',null,44,19,19),\
(10005,'2017-10-03','长沙',29,1,'2017-10-03 18:11:02','2017-10-03
18:11:02',3,1,1);
查看表
select * from test_db.example_site_visit;

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');
查询表
select * from test_db.user;

Uniq 模型完全可以用聚合模型中的 REPLACE 方式替代。其内部的实现方式和数据存储方式也完全一样。

Duplicate 模型

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

建表
CREATE TABLE IF NOT EXISTS test_db.example_log
(
 `timestamp` DATETIME NOT NULL COMMENT "日志时间",
 `type` INT NOT NULL COMMENT "日志类型",
 `error_code` INT COMMENT "错误码",
 `error_msg` VARCHAR(1024) COMMENT "错误详细信息",
 `op_id` BIGINT COMMENT "负责人 id",
 `op_time` DATETIME COMMENT "处理时间"
)
DUPLICATE KEY(`timestamp`, `type`)
DISTRIBUTED BY HASH(`timestamp`) BUCKETS 10;
插入数据
insert into test_db.example_log values\
('2017-10-01 08:00:05',1,404,'not found page', 101, '2017-10-01
08:00:05'),\
('2017-10-01 08:00:05',1,404,'not found page', 101, '2017-10-01
08:00:05'),\
('2017-10-01 08:00:05',2,404,'not found page', 101, '2017-10-01
08:00:06'),\
('2017-10-01 08:00:06',2,404,'not found page', 101, '2017-10-01
08:00:07');
查表

数据模型的选择

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

  1. Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。
  2. Uniq 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。
  3. Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)

聚合模型的局限性

做一些计算类的操作需要判断 例如count sum之类的操作

动态分区

动态分区在0.12加入,对表级别的分区做ttl,减少用户的操作负担

原理

动态分区需要用户建表时设置动态分区的规则。FE会启动一个后台线程,根据用户指定的规则创建或删除分区。用户也可对运行时对现有规则进行变更。

使用方式

建表

CREATE TABLE tbl1
(...)
PROPERTIES
(
 "dynamic_partition.prop1" = "value1",
 "dynamic_partition.prop2" = "value2",
 ...
)

运行时修改

ALTER TABLE tbl1 SET
(
 "dynamic_partition.prop1" = "value1",
 "dynamic_partition.prop2" = "value2",
  ...
)

动态分区规则参数

主要参数

动态分区规则参数都以dynamic_partition为前缀:

参数注解
dynamic_partition.enable是否开启动态分区特性,可指定 true 或 false,默认为 true600
dynamic_partition.time_unit动态分区调度的单位,可指定 HOUR、DAY、WEEK、MONTH。 HOUR,后缀格式为 yyyyMMddHH,分区列数据类型不能为DATE。DAY,后缀格式为 yyyyMMdd。WEEK,后缀格式为 yyyy_ww。即当前日期属于这一年的第几周。MONTH,后缀格式为 yyyyMM。
dynamic_partition.time_zone动态分区的时区,如果不填写,则默认为当前机器的系统的时区
dynamic_partition.start动态分区的起始偏移,为负数。根据 time_unit 属性的不同,以当天(星期/月)为基准,分区范围在此偏移之前的分区将会被删除。如果不填写默认值为 Interger.Min_VALUE 即-2147483648,即不删除历史分区
dynamic_partition.end动态分区的结束偏移,为正数。根据 time_unit 属性的不同,以当天(星期/月)为基准,提前创建对应范围的分区
dynamic_partition.prefix动态创建的分区名前缀
dynamic_partition.buckets动态创建的分区所对应分桶数量
dynamic_partition.replication_num动态创建的分区所对应的副本数量,如果不填写,则默认为该表创建时指定的副本数量。
dynamic_partition.start_day_of_week当 time_unit 为 WEEK 时,该参数用于指定每周的起始点。取值为 1 到 7。其中 1 表示周一,7 表示周日。默认为 1,即表示每周以周一为起始点
dynamic_partition.start_day_of_month当 time_unit 为 MONTH 时,该参数用于指定每月的起始日期。取值为 1 到 28。其中 1 表示每月 1 号,28 表示每月 28 号。默认为 1,即表示每月以 1 号位起始点。暂不支持以 29、30、31 号为起始日,以避免因闰年或闰月带来的歧义
创建历史分区的参数
  • dynamic_partition.create_history_partition
默认为 false。当置为 true 时,Doris 会自动创建所有分区,当期望创建的分区个数大
于 max_dynamic_partition_num 值时,操作将被禁止。当不指定 start 属性时,该参数不生
效。
  • dynamic_partition.create_history_partition
当 create_history_partition 为 true 时,该参数用于指定创建历史分区数量。默认值为 -
1, 即未设置。
  • dynamic_partition.hot_partition_num
指定最新的多少个分区为热分区。对于热分区,系统会自动设置其 storage_medium 参
数为 SSD,并且设置 storage_cooldown_time。
hot_partition_num 是往前 n 天和未来所有分区
  • dynamic_partition.reserved_history_periods
需要保留的历史分区的时间范围。当 dynamic_partition.time_unit 设置为
"DAY/WEEK/MONTH" 时,需要以 [yyyy-MM-dd,yyyy-MM-dd],[...,...] 格式进行设置。当
dynamic_partition.time_unit 设置为 "HOUR" 时,需要以 [yyyy-MM-dd HH:mm:ss,yyyyMM-dd HH:mm:ss],[...,...] 的格式来进行设置。如果不设置,默认为 "NULL"。```
创建历史分区的规则

假设需要创建的历史分区数据为expect_create_partition_num,具体参数如下

  • create_history_partition = true
  1. dynamic_partition.history_partition_num 未设置,即 -1.则 expect_create_partition_num = end - start;
  2. dynamic_partition.history_partition_num 已设置 则 expect_create_partition_num = end - max(start, -histoty_partition_num);
  • create_history_partition = false
    不会创建历史分区,expect_create_partition_num = end - 0;
  • 当 expect_create_partition_num > max_dynamic_partition_num(默认 500)时,禁止
    创建过多分区。

假如某些情况导致start与end之间的分区丢失,之间的分区会删除重建,但是start之前的并不会重新创建

具体建表语句

创建语句
create table student_dynamic_partition1
(id int,
time date,
name varchar(50),
age int
)
duplicate key(id,time)
PARTITION BY RANGE(time)()
DISTRIBUTED BY HASH(id) buckets 10
PROPERTIES(
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "10",
"replication_num" = "1"
);

查看动态分区表调度情况
SHOW DYNAMIC PARTITION TABLES;

查看表分区
SHOW PARTITIONS FROM student_dynamic_partition1;

修改成对应时间
insert into student_dynamic_partition1 values(1,'2022-03-3111:00:00','name1',18);

设置创建历史分区
ALTER TABLE student_dynamic_partition1 SET
("dynamic_partition.create_history_partition" = "true");

查看分区情况
SHOW PARTITIONS FROM student_dynamic_partition1;

HELP ALTER TABLE

ALTER TABLE tbl_name SET ("dynamic_partition.enable" = "false") 

假如从手动分区到动态分区,偏移量之前的分区会被删除

Rollup

通常我们创建的doris表可以称为base 表,在base表之上,我们可以创建任意多个ROLLUP表。这些ROLLUP表的数据是基于base表产生,并且物理上是独立存储的。他主要是在base表基础上获取更粗粒度的一些聚合数据

Uniq算是Aggregate一种特殊模式

查看表结构
desc tablename all;

alter table tablename add rollup rollupname(colname,colname);

查看rollup完成情况
SHOW ALTER TABLE ROLLUP;

Duplicate模式中的ROLLUP

因为duplicate中没有聚合,rollup 失去了上卷的含义,只是单纯的对前缀索引做一个排序

我们将一行数据的前36字节作为这行数据前缀索引,当遇到varchar就会被截断,所以在建表需要考虑好列的顺序。

rollup的一些说明

rollup最根本作用是做一些查询速率的优化,在源码中,将其命名为Materialized Index(物化索引)。
在查询操作时,并不能显示指定使用某个rollup,由系统决定。rollup数据独立存储,创建越多,占据存储越多,导入数据会变慢。但是查询成正比。rollup聚合方式与base表一致,无法改变。

物化视图

物化视图是包含了查询结果的数据库对象,是预先存储查询结果的一种数据库对象。

适用的一些场景

  • 分析需求覆盖明细数据查询以及固定维度查询两方面。
  • 查询仅涉及表中的很小一部分列或行。
  • 查询包含一些耗时处理操作,比如:时间很久的聚合操作等。
  • 查询需要匹配不同前缀索引。
对于那些经常重复的使用相同的子查询结果的查询性能大幅提升。
Doris 自动维护物化视图的数据,无论是新的导入,还是删除操作都能保证 base 表和物化视图表的数据一致性。无需任何额外的人工维护成本。
查询时,会自动匹配到最优物化视图,并直接从物化视图中读取数据。
自动维护物化视图的数据会造成一些维护开销

物化视图 VS Rollup

物化视图是后出的,之前物化视图的功能由rollup来做,物化视图弥补了rollup无法做明细数据的预聚合
ALTER TABLE ADD ROLLUP 的语句均可以通过CREATE MATERIALIZED VIEW 实现。

物化视图原理

创建物化视图
物化视图创建原则:
多个共用聚合和key
不需要给某个查询去做一个单独做一个物化视图


HELP CREATE MATERIALIZED VIEW
是一个异步的操作

一些简单物化视图相关的操作

SHOW ALTER TABLE MATERIALIZED VIEW FROM test_db; 

查看 Base 表的所有物化视图
desc sales_records all;

DROP MATERIALIZED VIEW 物化视图名 on Base 表名;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值