Doris数据库是一个分布式的列式存储数据库,它支持分区和分桶两种数据划分方式
Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。第二层是 Bucket(Tablet),仅支持 Hash 的划分方式。
Doris数据划分也可以仅使用一层分区。使用一层分区时,只支持 Bucket 划分
Partition,支持 Range 和 List
Range分区是一种基于分区键对表进行分区的方式,分区键指的是表中的某一列,而Range则是指这个列的值的范围。使用Range分区,可以将数据按照列的值的范围进行划分,将数据分散到不同的节点上,以实现分布式存储和查询
VALUES LESS THAN (...) 仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区间。也支持通过 VALUES [...) 指定上下界,生成一个左闭右开的区间
官方示例介绍
x-- Range Partitionuse test_db;
CREATE TABLE IF NOT EXISTS test_db.example_range_tbl( `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 "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间")ENGINE=OLAPAGGREGATE KEY(`user_id`, `date`, `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 16PROPERTIES( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2023-05-12 12:00:00");
如上
example_range_tbl示例,当建表完成后,会自动生成如下3个分区:xxxxxxxxxxp201701: [MIN_VALUE, 2017-02-01)p201702: [2017-02-01, 2017-03-01)p201703: [2017-03-01, 2017-04-01)
xxxxxxxxxxMySQL [test_db]> show partitions from example_range_tbl;xxxxxxxxxxinsert into test_db.example_range_tbl values (10000,'2017-01-01','北京',20,0,'2017-01-01 06:00:00',20,10,10)验证数据是否在对应分区内
xxxxxxxxxxMySQL [test_db]> SELECT user_id,date FROM example_range_tbl PARTITION `p201701`;+---------+------------+| user_id | date |+---------+------------+| 10000 | 2017-01-01 |+---------+------------+1 row in set (0.01 sec)
MySQL [test_db]> SELECT user_id,date FROM example_range_tbl PARTITION `p201702`;Empty set (0.01 sec)
MySQL [test_db]> SELECT user_id,date FROM example_range_tbl PARTITION `p201703`;Empty set (0.00 sec)xxxxxxxxxxMySQL [test_db]> insert into test_db.example_range_tbl values (20000,'2017-11-01','北京',20,0,'2017-11-01 06:00:00',20,10,10);Query OK, 0 rows affected, 1 warning (0.04 sec){'label':'insert_1bb0bf5655344769-b3479bb93baa9a5a', 'status':'VISIBLE', 'txnId':'53'}
MySQL [test_db]> select user_id,date from example_range_tbl;+---------+------------+| user_id | date |+---------+------------+| 10000 | 2017-01-01 |+---------+------------+1 row in set (0.01 sec)插入的这条数据,'2017-11-01' 没有对应的分区, 插入不成功, 但不会抛出异常或错误。( 查询结果里没有新插入的这条数据 )
xxxxxxxxxxalter table example_range_tbl add partition p201705 values less than ('2017-06-01');当alter执行完成后,新的分区信息如下:
xxxxxxxxxxp201701: [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)
xxxxxxxxxxalter table example_range_tbl drop partition p201703;当alter执行完成后,新的分区信息如下:
xxxxxxxxxxp201701: [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) 即如果导入的数据范围在这个空洞范围内,数据是无法导入的,虽然不会抛出异常或错误,但处于这个空洞区间的数据相当于抹去了。
建表示例:
xxxxxxxxxxCREATE TABLE IF NOT EXISTS test_db.example_range_tbl_more( `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 "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间")ENGINE=OLAPAGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)PARTITION BY RANGE(`date`, `user_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"))DISTRIBUTED BY HASH(`user_id`) BUCKETS 16PROPERTIES( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2023-05-12 12:00:00");在以上示例中,我们指定 date(DATE 类型) 和 id(INT 类型) 作为分区列。以上示例最终得到的分区如下:
xxxxxxxxxx* 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。当用户插入数据时,分区列值会按照顺序依次比较,最终得到对应的分区,举例如下:
xxxxxxxxxx* 数据 --> 分区* 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 --> 无法导入
官方示例介绍
xxxxxxxxxx-- List Partition
CREATE TABLE IF NOT EXISTS test_db.example_list_tbl( `user_id` LARGEINT NOT NULL COMMENT "用户id", `date` DATE NOT NULL COMMENT "数据灌入日期时间", `city` VARCHAR(20) NOT NULL 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=olapAGGREGATE KEY(`user_id`, `date`, `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 16PROPERTIES( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2023-05-12 12:00:00");如上
example_list_tbl示例,当建表完成后,会自动生成如下3个分区:xxxxxxxxxxp_cn: ("Beijing", "Shanghai", "Hong Kong")p_usa: ("New York", "San Francisco")p_jp: ("Tokyo")
xxxxxxxxxxMySQL [test_db]> insert into test_db.example_list_tbl values (10000,'2017-01-01','Beijing',20,0,'2017-01-01 06:00:00',20,10,10);Query OK, 1 row affected (0.04 sec){'label':'insert_92a0193c3d65454a-a9d71623490c05eb', 'status':'VISIBLE', 'txnId':'66'}
MySQL [test_db]> select user_id,date from example_list_tbl; +---------+------------+| user_id | date |+---------+------------+| 10000 | 2017-01-01 |+---------+------------+1 row in set (0.02 sec)xxxxxxxxxxMySQL [test_db]> SELECT user_id,date FROM example_list_tbl PARTITION `p_cn`;+---------+------------+| user_id | date |+---------+------------+| 10000 | 2017-01-01 |+---------+------------+1 row in set (0.01 sec)
MySQL [test_db]> SELECT user_id,date FROM example_list_tbl PARTITION `p_usa`;Empty set (0.00 sec)
MySQL [test_db]> SELECT user_id,date FROM example_list_tbl PARTITION `p_jp`;Empty set (0.00 sec)这里和上方Range分区相同,不展开测试插入删除数据等操作
xxxxxxxxxxalter table example_list_tbl add partition p_uk values IN ("London");当我们增加一个分区 p_uk VALUES IN ("London"),分区结果如下:
xxxxxxxxxxp_cn: ("Beijing", "Shanghai", "Hong Kong")p_usa: ("New York", "San Francisco")p_jp: ("Tokyo")p_uk: ("London")
分桶语法DISTRIBUTED BY HASH(user_id) BUCKETS 16
如果使用了 Partition,则 DISTRIBUTED ... 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则
分桶列可以是多列,Aggregate 和 Unique 模型必须为 Key 列,Duplicate 模型可以是 key 列和 value 列。分桶列可以和 Partition 列相同或不同
分桶列的选择,是在 查询吞吐 和 查询并发 之间的一种权衡
Auto Bucket : 根据数据量,计算分桶数。 对于分区表,可以根据历史分区的数据量、机器数、盘数,确定一个分桶
分桶的数量理论上没有上限
ADD PARTITION),可以单独指定新分区的 Bucket 数量。可以利用这个功能方便的应对数据缩小或膨胀。
复合分区
以下场景推荐使用复合分区
用户也可以不使用复合分区,即使用单分区。则数据只做 HASH 分布。