Apache doris 数据模型 (hf200012.github.io)
一. 数据划分
Row & Column
一张表包括行(Row)和列(Column);
Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。
doris中的列分为两类:key列和value列
key列在doris中有两种作用:
聚合表模型中,key是聚合和排序的依据
其他表模型中,key是排序依据
分区与分桶
- partition(分区):是在逻辑上将一张表按行(横向)划分
- tablet(又叫bucket,分桶):在物理上对一个分区再按行(横向)划分
range分区创建语法
CREATE TABLE IF NOT EXISTS test.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 "用户性别"
)
ENGINE=OLAP
DUPLICATE KEY(`user_id`, `date`) -- 表模型
-- 分区的语法
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 1;
Bucket
- 如果使用了 Partition,则 DISTRIBUTED ... 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。
- 分桶列可以是多列,但必须为 Key 列。分桶列可以和 Partition 列相同或不同。
- 分桶列的选择,是在 查询吞吐 和 查询并发 之间的一种权衡:
- 如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。
- 如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的IO影响较小(尤其当不同桶分布在不同磁盘上时),所以这种方式适合高并发的点查询场景。
二. 数据表设计
- partition(分区):是在逻辑上将一张表按行(横向)划分
- tablet(又叫bucket,分桶):在物理上对一个分区再按行(横向)划分
Duplicate 明细模型
CREATE TABLE IF NOT EXISTS demo.duplicate_demo
(
`timestamp` DATETIME NOT NULL COMMENT "日志时间",
`type` INT NOT NULL COMMENT "日志类型",
`action` VARCHAR(1024) NOT NULL COMMENT "日志",
`op_time` DATETIME COMMENT "处理时间"
)
DUPLICATE KEY(`timestamp`, `type`)
DISTRIBUTED BY HASH(`timestamp`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序,在 DUPLICATE KEY 的选择上,建议适当的选择前 2-4 列
数据导入测试
insert into demo.duplicate_demo values\
('2017-10-01 08:00:05',1,'not found page', '2017-10-01 08:00:05'),\
('2017-10-01 08:00:05',1,'not found page', '2017-10-01 08:00:05'),\
('2017-10-01 08:00:05',2,'not found page', '2017-10-01 08:00:06'),\
('2017-10-01 08:00:06',2,'not found page', '2017-10-01 08:00:07');
Aggregate 聚合模型
是相同key的数据进行自动聚合的表模型。表中的列按照是否设置了 AggregationType,分为 Key(维度列)和 Value(指标列),没有设置 AggregationType 的称为 Key,设置了 AggregationType 的称为 Value。当我们导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的AggregationType 进行聚合。AggregationType 目前有以下四种聚合方式:
- SUM:求和,多行的 Value 进行累加。
- REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
- REPLACE_IF_NOT_NULL :当遇到 null 值则不更新。
- MAX:保留最大值。
- MIN:保留最小值。
-- 这是一个用户消费和行为记录的数据表
CREATE TABLE IF NOT EXISTS ex_user
(
`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=olap
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
-- 分区
-- 分桶
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
insert into ex_user values\
(10000,'2017-10-01','北京',20,0,'2017-10-01 06:00:00',20,10,10),\
(10000,'2017-10-01','北京',20,0,'2017-10-01 07:00:00',15,2,2),\
(10001,'2017-10-01','北京',30,1,'2017-10-01 17:05:45',2,22,22),\
(10002,'2017-10-02','上海',20,1,'2017-10-02 12:59:12',200,5,5),\
(10003,'2017-10-02','广州',32,0,'2017-10-02 11:20:00',30,11,11),\
(10004,'2017-10-01','深圳',35,0,'2017-10-01 10:00:15',100,3,3),\
(10004,'2017-10-03','深圳',35,0,'2017-10-03 10:20:22',11,6,6);
数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。聚合模型的局限性
例如在:在 count() 查询中,Doris 必须扫描所有的 AGGREGATE KEY 列,并且聚合后,才能得到语意正确的结果,当聚合列非常多时,count() 查询需要扫描大量的数据,效率低下。因此, 当业务上有频繁的 count(*) 查询时 ,我们建议用户 通过增加一个值恒为 1 的,聚合类型为 SUM 的列来模拟 count
UNIQUE 模型
是相同key的数据进行自动去重的表模型。在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性,即如何获得 Primary Key 唯一性约束。因此,引入了 Uniq 的数据模型。该模型本质上是聚合模型的一个特例,也是一种简化的表结构表示方式。
Uniq 模型完全可以用聚合模型中的 REPLACE 方式替代。其内部的实现方式和数据存储方式也完全一样。
CREATE TABLE IF NOT EXISTS user
(
-- key列
`user_id` LARGEINT NOT NULL COMMENT "用户 id",
`username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
-- value列
`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 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
insert into user values\
(10000,'zss','北京',18,0,12345678910,'北京朝阳区 ','2017-10-01 07:00:00'),\
(10000,'zss','北京',19,0,12345678910,'北京顺义区 ','2018-10-01 07:00:00'),\
(10000,'lss','北京',20,0,12345678910,'北京海淀区','2017-11-15 06:10:20');
Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。
Unique 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。
Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。
三. 索引
索引用于帮助快速过滤或查找数据。
目前 Doris 主要支持两类索引:
- 内建的智能索引:包括前缀索引和 ZoneMap 索引。
- 用户创建的二级索引:包括 Bloom Filter 索引 和 Bitmap倒排索引。
其中 ZoneMap 索引是在列存格式上,对每一列自动维护的索引信息,包括 Min/Max,Null 值个数等等。这种索引对用户透明。
doris中,对于前缀索引有如下约束:
- 他的索引键最大长度是36个字节
-
当他遇到了varchar数据类型的时候,即使没有超过36个字节,也会自动截断
自动截断
Doris BloomFilter适用场景
满足以下几个条件时可以考虑对某列建立Bloom Filter 索引:
- BloomFilter是在无法利用前缀索引的查询场景中,来加快查询速度的。
- 查询会根据该列高频过滤,而且查询条件大多是 in 和 = 过滤。
- 不同于Bitmap, BloomFilter适用于高基数列。比如UserID。因为如果创建在低基数的列上,比如 “性别” 列,则每个Block几乎都会包含所有取值,导致BloomFilter索引失去意义。字段随机
Doris BloomFilter使用注意事项 - 不支持对Tinyint、Float、Double 类型的列建Bloom Filter索引。
- Bloom Filter索引只对 in 和 = 过滤查询有加速效果。
- 可以通过explain来查看命中了哪种索引 --没办法查看
Bitmap 索引
Bitmap 索引CREATE INDEX [IF NOT EXISTS] index_name ON table1 (siteid) USING BITMAP COMMENT 'balabala';
create index 索引名称 on 表名(给什么字段创建bitmap索引) using bitmap COMMENT 'balabala';
create index user_id_bitmap on sale_detail_bloom(sku_id) USING BITMAP COMMENT '使用user_id创建的bitmap索引';
- bitmap 索引仅在单列上创建。
- bitmap 索引能够应用在 Duplicate、Uniq 数据模型的所有列和 Aggregate模型的key列上。
- bitmap 索引支持的数据类型如下:(老版本只支持bitmap类型)
TINYINT,SMALLINT,INT,BIGINT,CHAR,VARCHAR,DATE,DATETIME,LARGEINT,DECIMAL,BOOL - bitmap索引仅在 Segment V2 下生效(Segment V2是升级版本的文件格式)。当创建 index 时,表的存储格式将默认转换为 V2 格式
四. Rollup
ROLLUP 在多维分析中是“上卷”的意思,即将数据按某种指定的粒度进行进一步聚合。
alter table aggregate表名 add rollup "rollup表的表名" (user_id,city,date,cost);
alter table ex_user add rollup rollup_ucd_cost(user_id,city,date,cost);
alter table ex_user add rollup rollup_u_cost(user_id,cost);
alter table ex_user add rollup rollup_cd_cost(city,date,cost);
alter table ex_user drop rollup rollup_u_cost;
alter table ex_user drop rollup rollup_cd_cost;
--如果是replace聚合类型得value,需要指定所有得key
-- alter table ex_user add rollup rollup_cd_visit(city,date,last_visit_date);
-- ERROR 1105 (HY000): errCode = 2, detailMessage = Rollup should contains
-- all keys if there is a REPLACE value
--添加完成之后可以show一下,看看底层得rollup有没有执行完成
SHOW ALTER TABLE ROLLUP;
Doris 会自动命中这个 ROLLUP 表,从而只需扫描极少的数据量,即可完成这次聚合查询。
explain SELECT user_id, sum(cost) FROM ex_user GROUP BY user_id;
五. 物化视图
就是查询结果预先存储起来的特殊的表。物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询
物化视图 VS Rollup
- 明细模型表下,rollup和物化视图的差别:
物化视图:都可以实现预聚合,新增一套前缀索引
rollup:对于明细模型,新增一套前缀索引 - 聚合模型下,功能一致
drop table sales_records;
create table sales_records(
record_id int,
seller_id int,
store_id int,
sale_date date,
sale_amt bigint)
duplicate key (record_id,seller_id,store_id,sale_date)
distributed by hash(record_id) buckets 2
properties("replication_num" = "1");
-- 插入数据
insert into sales_records values \
(1,1,1,'2022-02-02',100),\
(2,2,1,'2022-02-02',200),\
(3,3,2,'2022-02-02',300),\
(4,3,2,'2022-02-02',200),\
(5,2,1,'2022-02-02',100),\
(6,4,2,'2022-02-02',200),\
(7,7,3,'2022-02-02',300),\
(8,2,1,'2022-02-02',400),\
(9,9,4,'2022-02-02',100);
-- 不同门店,看总销售额的一个场景
select store_id, sum(sale_amt)
from sales_records
group by store_id;
CREATE MATERIALIZED VIEW store_id_sale_amonut as
select store_id, sum(sale_amt)
from sales_records
group by store_id;
CREATE MATERIALIZED VIEW store_amt as
select store_id, sum(sale_amt) as sum_amount
from sales_records
group by store_id;
--针对上述场景做一个物化视图
create materialized view store_amt as
select store_id, sum(sale_amt) as sum_amount
from sales_records
group by store_id;
show alter table materialized view from 库名 order by CreateTime desc limit 1;
show alter table materialized view from test order by CreateTime desc limit 1;
+-------+---------------+---------------------+---------------------+---------------+-----------------+----------+---------------+----------+------+----------+---------+
| JobId | TableName | CreateTime | FinishTime | BaseIndexName | RollupIndexName | RollupId | TransactionId | State | Msg | Progress | Timeout |
+-------+---------------+---------------------+---------------------+---------------+-----------------+----------+---------------+----------+------+----------+---------+
| 15093 | sales_records | 2022-11-25 10:32:33 | 2022-11-25 10:32:59 | sales_records | store_amt | 15094 | 3008 | FINISHED | | NULL | 86400 |
+-------+---------------+---------------------+---------------------+---------------+-----------------+----------+---------------+----------+------+----------+---------+
查看 Base 表的所有物化视图
desc sales_records all;
+---------------+---------------+-----------+--------+------+-------+---------+-------+---------+
| IndexName | IndexKeysType | Field | Type | Null | Key | Default | Extra | Visible |
+---------------+---------------+-----------+--------+------+-------+---------+-------+---------+
| sales_records | DUP_KEYS | record_id | INT | Yes | true | NULL | | true |
| | | seller_id | INT | Yes | true | NULL | | true |
| | | store_id | INT | Yes | true | NULL | | true |
| | | sale_date | DATE | Yes | true | NULL | | true |
| | | sale_amt | BIGINT | Yes | false | NULL | NONE | true |
| | | | | | | | | |
| store_amt | AGG_KEYS | store_id | INT | Yes | true | NULL | | true |
| | | sale_amt | BIGINT | Yes | false | NULL | SUM | true |
+---------------+---------------+-----------+--------+------+-------+---------+-------+---------+
EXPLAIN SELECT store_id, sum(sale_amt) FROM sales_records GROUP BY store_id;
Create olap table should contain distribution desc
be节点只有一个,建表遇到问题,没加 DISTRIBUTED BY HASH()
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
创建分区时不可添加范围重叠的分区,Doris导数据时不会主动创建分区,要导入数据先创建对应的分区
able[sales_records]'s state is not NORMAL. Do not allow doing materialized view
doris的物化视图中,不支持count(distint) ==> bitmap_union
bitmap_union(to_bitmap(user_id)) uv_bitmap
动态分区创建
Bloom Filter 索引
索引性能测试
本地导入的方式上传数据:(1000万数据)
curl --location-trusted -u root: -T /opt/output.txt -H "label:dwd_policy_main_d" -H "column_separator:," http://127.0.0.1:8040/api/demo/dwd_policy_main_d/_stream_load
网友评论