数据划分
列定义
这里我们只以 AGGREGATE KEY 数据模型为例进行说明。
以AGGREGATE KEY数据模型为例进行说明。更多数据模型参阅Doris数据模型。
列的基本类型,可以通过在mysql-client中执行HELP CREATE TABLE; 查看。
AGGREGATE KEY数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为Key列。而其余则为Value列。
定义列时,可参照如下建议:
- Key 列必须在所有Value列之前。
- 尽量选择整型类型。因为整型类型的计算和查找比较效率远高于字符串。
- 对于不同长度的整型类型的选择原则,遵循够用即可。
- 对于VARCHAR和STRING类型的长度,遵循够用即可。
所有列的总字节长度(包括 Key和Value)不能超过100KB
ENGINE(引擎)
Doris 支持的引擎有:olap|mysql|broker|hive
olap 是默认的引擎, 在 Doris 中,只有这个 ENGINE 类型是由 Doris 负责数据管理和存储的。其他 ENGINE 类型,如 mysql、broker、es、hive等等,本质上只是对外部其他数据库或系统中的表的映射,以保证 Doris 可以读取这些数据。而 Doris 本身并不创建、管理和存储任何非 olap ENGINE 类型的表和数据
分区和分桶
Doris支持两层的数据划分。第一层是 Partition,支持 Range和List的划分方式。第二层是 Bucket(Tablet),仅支持Hash的划分方式。
也可以仅使用一层分区。使用一层分区时,只支持Bucket划分。
分区(partiton)
Partition列可以指定一列或多列。分区列必须为KEY列。多列分区的使用方式在后面介绍。
不论分区列是什么类型,在写分区值时,都需要加双引号。
分区数量理论上没有上限。
当不使用Partition建表时,系统会自动生成一个和表名同名的,全值范围的 Partition。
该Partition对用户不可见,并且不可删改。创建分区时不可添加范围重叠的分区。
Rang 分区(范围分区)
分区列通常为时间列( PARTITION BY RANGE(`date`) ),以方便的管理新旧数据
VALUES LESS THAN (...) 仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区间。
VALUES [...) 指定上下界,生成一个左闭右开的区间。
通过 VALUES [...) 同时指定上下界比较容易理解。这里举例说明,当使用VALUES LESS THAN (...) 语句进行分区的增删操作时,分区范围的变化情况:
- 如上 example_range_tbl 示例,当建表完成后,会自动生成如下3个分区:
p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201703: [2017-03-01, 2017-04-01)
查看一个表的所有分区信息
show partitions from example_range_tbl;
插入数据:
进入 p201701 分区
insert into test_db.example_range_tbl values (10000,'2017-01-01','北京',20,0,'2017-01-01 06:00:00',20,10,10);
没有对应的分区, 插入不成功,抛出异常
insert into test_db.example_range_tbl values (20000,'2017-11-01','北京',20,0,'2017-11-01 06:00:00',20,10,10);
- 当我们增加一个分区 p201705 VALUES LESS THAN ("2017-06-01"),分区结果如下。
新增分区语法:
alter table example_range_tbl add partition 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)
- 此时我们删除分区 p201703,则分区结果如下:
删除分区语法:
alter table example_range_tbl drop partition 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) 即如果导入的数据范围在这个空洞范围内,是无法导入的。
综上,分区的删除不会改变已存在分区的范围。删除分区可能出现空洞。通过 VALUES LESS THAN 语句增加分区时,分区的下界紧接上一个分区的上界。
List 分区(列表分区)
分区列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 数据类型,分区值为枚举值。只有当数据为目标分区枚举值其中之一时,才可以命中分区
Partition 支持通过 VALUES IN (...) 来指定每个分区包含的枚举值。
如上 example_list_tbl 示例,当建表完成后,会自动生成如下3个分区:
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")
进入 p_cn 分区
insert into test_db.example_list_tbl values (10000,'2017-01-01','Beijing',20,0,'2017-01-01 06:00:00',20,10,10);
插入不成功, 不进入任何分区,且报错
insert into test_db.example_list_tbl values (20000,'2017-01-01','shenzhen',20,0,'2017-01-01 06:00:00',20,10,10);
当我们增加一个分区 p_uk VALUES IN ("London"),分区结果如下:
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")
p_uk: ("London")
当我们删除分区 p_jp,分区结果如下:
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_uk: ("London")
分桶(Bucket)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
如果使用了 Partition,则 DISTRIBUTED ... 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。
分桶列可以是多列,但必须为 Key 列。分桶列可以和 Partition 列相同或不同。
分桶的数量理论上没有上限。
分桶列的选择,是在查询吞吐和查询并发之间的一种权衡:
如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。
如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的IO影响较小(尤其当不同桶分布在不同磁盘上时),所以这种方式适合高并发的点查询场景。
复合分区与单分区
复合分区:分区和分桶
单分区:只分桶(其实是所有数据在一个分区,数据只做 hash 分布)
以下场景推荐使用复合分区
有时间维度或类似带有有序值的维度,可以以这类维度列作为分区列。分区粒度可以根据导入频次、分区数据量等进行评估。
历史数据删除需求:如有删除历史数据的需求(比如仅保留最近N 天的数据)。使用复合分区,可以通过删除历史分区来达到目的。也可以通过在指定分区内发送 DELETE 语句进行数据删除。
解决数据倾斜问题:每个分区可以单独指定分桶数量。如按天分区,当每天的数据量差异很大时,可以通过指定分区的分桶数,合理划分不同分区的数据,分桶列建议选择区分度大的列。
多列分区
Doris 支持指定多列作为分区列
- 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 ---> 无法导入
PROPERTIES
- replication_num
副本数。默认副本数为3。如果 BE 节点数量小于3,则需指定副本数小于等于 BE 节点数量。
- storage_medium/storage_cooldown_time
storage_medium 用于声明表数据的初始存储介质,而 storage_cooldown_time 用于设定到期时间。
"storage_medium" = "SSD",
"storage_cooldown_time" = "2020-11-20 00:00:00"
这个示例表示数据存放在 SSD 中,并且在 2020-11-20 00:00:00 到期后,会自动迁移到 HDD 存储上。
动态分区
前面的分区, 必须在插入数据之前手动添加需要的分区, 使用颇多不变.
动态分区是在 Doris 0.12 版本中引入的新功能。旨在对表级别的分区实现生命周期管理(TTL),减少用户的使用负担。
目前实现了动态添加分区及动态删除分区的功能。
动态分区只支持 Range 分区
原理
在某些使用场景下,用户会将表按照天进行分区划分,每天定时执行例行任务,这时需要使用方手动管理分区,否则可能由于使用方没有创建分区导致数据导入失败,这给使用方带来了额外的维护成本。
通过动态分区功能,用户可以在建表时设定动态分区的规则。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,默认为true 如果为 FALSE,则 Doris 会忽略该表的动态分区规则。 |
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.hot_partition_num | 指定最新的多少个分区为热分区。对于热分区,系统会自动设置其 storage_medium 参数为SSD,并且设置 storage_cooldown_time。 hot_partition_num 是往前 n 天和未来所有分区 我们举例说明。假设今天是 2021-05-20,按天分区,动态分区的属性设置为:hot_partition_num=2, end=3, start=-3。则系统会自动创建以下分区,并且设置 storage_medium 和 storage_cooldown_time 参数: p20210517:["2021-05-17", "2021-05-18") storage_medium=HDD storage_cooldown_time=9999-12-31 23:59:59 p20210518:["2021-05-18", "2021-05-19") storage_medium=HDD storage_cooldown_time=9999-12-31 23:59:59 p20210519:["2021-05-19", "2021-05-20") storage_medium=SSD storage_cooldown_time=2021-05-21 00:00:00 p20210520:["2021-05-20", "2021-05-21") storage_medium=SSD storage_cooldown_time=2021-05-22 00:00:00 p20210521:["2021-05-21", "2021-05-22") storage_medium=SSD storage_cooldown_time=2021-05-23 00:00:00 p20210522:["2021-05-22", "2021-05-23") storage_medium=SSD storage_cooldown_time=2021-05-24 00:00:00 p20210523:["2021-05-23", "2021-05-24") storage_medium=SSD storage_cooldown_time=2021-05-25 00:00:00 |
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,yyyy-MM-dd HH:mm:ss],[...,...] 的格式来进行设置。如果不设置,默认为 "NULL"。 我们举例说明。假设今天是 2021-09-06,按天分类,动态分区的属性设置为: time_unit="DAY", \ end=3, \ start=-3, \ reserved_history_periods="[2020-06-01,2020-06-20],[2020-10-31,2020-11-15]"。 则系统会自动保留: ["2020-06-01","2020-06-20"], ["2020-10-31","2020-11-15"] 或者 time_unit="HOUR", \ end=3, \ start=-3, \ reserved_history_periods="[2020-06-01 00:00:00,2020-06-01 03:00:00]". 则系统会自动保留: ["2020-06-01 00:00:00","2020-06-01 03:00:00"] 这两个时间段的分区。其中,reserved_history_periods 的每一个 [...,...] 是一对设置项,两者需要同时被设置,且第一个时间不能大于第二个时间``。 |
创建历史分区规则
当 create_history_partition 为 true,即开启创建历史分区功能时,Doris 会根据 dynamic_partition.start 和 dynamic_partition.history_partition_num 来决定创建历史分区的个数。
假设需要创建的历史分区数量为 expect_create_partition_num,根据不同的设置具体数量如下:
- create_history_partition = true
① dynamic_partition.history_partition_num 未设置,即 -1。
则expect_create_partition_num = end - start + 1;
② dynamic_partition.history_partition_num 已设置
则expect_create_partition_num = end - max(start, -histoty_partition_num) + 1;
- create_history_partition = false
不会创建历史分区,expect_create_partition_num = end - 0 + 1;
- 当 expect_create_partition_num > max_dynamic_partition_num(默认500)时,禁止创建过多分区。
总结:今天的分区(1) + end(未来的分区) + 过去的分区(start 和 history-num 谁少听谁的)
假设今天是 2021-05-20,按天分区,动态分区的属性设置为:create_history_partition=true, end=3, start=-3, history_partition_num=1,则系统会自动创建以下分区:
p20210519
p20210520
p20210521
p20210522
p20210523
history_partition_num=5,其余属性与 1 中保持一直,则系统会自动创建以下分区:
p20210517
p20210518
p20210519
p20210520
p20210521
p20210522
p20210523
history_partition_num=-1 即不设置历史分区数量,其余属性与 1 中保持一直,则系统会自动创建以下分区:
p20210517
p20210518
p20210519
p20210520
p20210521
p20210522
p20210523
注意事项
动态分区使用过程中,如果因为一些意外情况导致dynamic_partition.start 和 dynamic_partition.end 之间的某些分区丢失,那么当前时间与 dynamic_partition.end 之间的丢失分区会被重新创建,dynamic_partition.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`)
PROPERTIES(
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.create_history_partition" = "true",
"dynamic_partition.history_partition_num" = "3",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "10",
"replication_num" = "1"
);
查看动态分区表调度情况
SHOW DYNAMIC PARTITION TABLES
LastUpdateTime: 最后一次修改动态分区属性的时间
LastSchedulerTime: 最后一次执行动态分区调度的时间
State: 最后一次执行动态分区调度的状态
LastCreatePartitionMsg: 最后一次执行动态添加分区调度的错误信息
LastDropPartitionMsg: 最后一次执行动态删除分区调度的错误信息
查看表的分区情况
SHOW PARTITIONS FROM student_dynamic_partition1\G
插入测试数据(需要修改日期)
insert into student_dynamic_partition1 values(1,'2023-07-11 11:00:00','name1',18);
insert into student_dynamic_partition1 values(1,'2023-07-12 11:00:00','name1',18);
insert into student_dynamic_partition1 values(1,'2023-07-13 11:00:00','name1',18);
动态分区表与手动分区表相互转换
对于一个表来说,动态分区和手动分区可以自由转换,但二者不能同时存在,有且只有一种状态。
手动分区转换为动态分区
如果一个表在创建时未指定动态分区,可以通过ALTER TABLE在运行时修改动态分区相关属性来转化为动态分区,具体示例可以通过HELP ALTER TABLE查看。
注意:如果已设定dynamic_partition.start,分区范围在动态分区起始偏移之前的历史分区将会被删除。
动态分区转换为手动分区
ALTER TABLE tbl_name SET ("dynamic_partition.enable" = "false")
关闭动态分区功能后,Doris将不再自动管理分区,需要用户手动通过ALTER TABLE 的方式创建或删除分区。