简介
Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库,以极速易用的特点被人们所熟知,仅需亚秒级响应时间即可返回海量数据下的查询结果,不仅可以支持高并发的点查询场景,也能支持高吞吐的复杂分析场景 基于此,Apache Doris 能够较好的满足报表分析、即席查询、统一数仓构建、数据湖联邦查询加速等使用场景,用户可以在此之上构建用户行为分析、AB 实验平台、日志检索分析、用户画像分析、订单分析等应用。 Apache Doris 最早是诞生于百度广告报表业务的 Palo 项目,2017 年正式对外开源,2018 年 7 月由百度捐赠给 Apache 基金会进行孵化,之后在 Apache 导师的指导下由孵化器项目管理委员会成员进行孵化和运营。目前 Apache Doris 社区已经聚集了来自不同行业数百家企业的 400 余位贡献者,并且每月活跃贡献者人数也超过 100 位。 2022 年 6 月,Apache Doris 成功从 Apache 孵化器毕业,正式成为 Apache 顶级项目(Top-Level Project,TLP) Apache Doris 如今在中国乃至全球范围内都拥有着广泛的用户群体,截止目前, Apache Doris 已经在全球超过 2000 家企业的生产环境中得到应用,在中国市值或估值排行前 50 的互联网公司中,有超过 80% 长期使用 Apache Doris,包括百度、美团、小米、京东、字节跳动、腾讯、网易、快手、微博、贝壳等。同时在一些传统行业如金融、能源、制造、电信等领域也有着丰富的应用。-基于 Apache Doris 可以构建统一实时数仓。 中文文档地址:https://doris.apache.org/zh-CN/docs/get-starting/quick-start
使用
数据模型
在 Doris 中,数据以表(Table)的形式进行逻辑上的描述 一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段 Column 可以分为两大类:Key 和 Value。从业务角度看,Key 和 Value 可以分别对应维度列和指标列 Doris 的数据模型主要分为3类: Aggregate,Unique,Duplicate
aggregate 模型
表中的列按照是否设置了 AggregationType,分为 Key (维度列) 和 Value(指标列) 当我们导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的 AggregationType 进行聚合 有以下四种聚合方式:
SUM:求和,多行的 Value 进行累加。 REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。 MAX:保留最大值。 MIN:保留最小值。 当向表中插入数据时,聚合函数会起作用,经过聚合,Doris 中最终只会存储聚合后的数据 注意:明细数据会丢失,用户不能够再查询到聚合前的明细数据了 AGGREGATE KEY 数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为 Key 列。而其余则为 Value 列 字段定义时遵循以下建议:
Key 列必须在所有 Value 列之前 尽量选择整型类型。因为整型类型的计算和查找效率远高于字符串 对于不同长度的整型类型的选择原则,遵循 够用即可 对于 VARCHAR 和 STRING 类型的长度,遵循 够用即可 所有列的总字节长度(包括 Key 和 Value)不能超过 100KB 示例:
DROP TABLE IF EXISTS signal. performance_stats_aggregate;
CREATE TABLE IF NOT EXISTS signal. performance_stats_aggregate
(
` time_stamp` DATETIME NOT NULL COMMENT "雷达上报时间" ,
` region_id` INT NOT NULL COMMENT '路口所属区域编号' ,
` intersection_number` INT NOT NULL COMMENT "路口ID" ,
` detector_nbr` VARCHAR ( 16 ) NOT NULL COMMENT "设备编号" ,
` approach` TINYINT NOT NULL COMMENT "进口:0-南向SB,1-西南向SW,2-西向WB,3-西北向NW,4-北向NB,5-东北向NE,6-东向EB,7-东南向SE,9-其他OTHER" ,
` movement` TINYINT NOT NULL COMMENT "流向:1-直行THROUGH,2-左转LEFT,3-右转RIGHT,4-掉头U_TURN,5-直左TL,6-直右TR,7-三向TLR,8-左右LR,9-直掉UT_TURN,10-左掉UL_TURN,99-其他OTHER" ,
` lane_nbr` TINYINT NOT NULL COMMENT "车道号" ,
` detector_zone_nbr` TINYINT REPLACE_IF_NOT_NULL COMMENT '检测区域(线)编号' ,
` detector_coil_nbr` TINYINT REPLACE_IF_NOT_NULL COMMENT '线圈编号' ,
` stats_period` SMALLINT REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '单位:秒' ,
` traffic_flow` SMALLINT REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '总流量' ,
` avg_speed` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '平均速度:单位:Km/h' ,
` time_share` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '时间占有率:单位:%' ,
` density` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '空间占有率:单位:%' ,
` time_headway` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '车头时距 单位:s' ,
` space_headway` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '车头间距 单位:m' ,
` queue_length` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '最大排队长度:单位:米' ,
` stops` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '平均停车次数单位:次' ,
` delay` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '平均延误单位:s' ,
` travel_time` Float REPLACE_IF_NOT_NULL DEFAULT "0" COMMENT '平均旅行时间:单位:s(只计算进口道,出口道默认0)'
)
AGGREGATE KEY ( ` time_stamp` , ` region_id` , ` intersection_number` , ` detector_nbr` , ` approach` , ` movement` , ` lane_nbr` )
DISTRIBUTED BY HASH ( ` region_id` , ` intersection_number` ) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
) ;
Unique 模型
在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性,即如何获得 Primary Key 唯一性约束。因此引入了 Unique 的数据模型 该模型本质上是聚合模型的一个特例,也是一种简化的表结构表示方式 Unique 模型完全可以用聚合模型中的REPLACE方式替代。其内部的实现方式和数据存储方式也完全一样 示例:
DROP TABLE IF EXISTS signal. traffic_event_unique;
CREATE TABLE IF NOT EXISTS signal. traffic_event_unique
(
` time_stamp` DATETIME ( 3 ) NOT NULL COMMENT "事件状态开始时间" ,
` region_id` INT NOT NULL COMMENT '路口所属区域编号' ,
` intersection_number` INT NOT NULL COMMENT "路口ID" ,
` detector_nbr` VARCHAR ( 16 ) NOT NULL COMMENT "设备编号" ,
` approach` TINYINT NOT NULL COMMENT "进口:0-南向SB,2-西向WB,4-北向NB,6-东向EB,9-其他OTHER" ,
` lane_nbr` TINYINT NOT NULL DEFAULT "0" COMMENT "车道号(部分事件不关联车道赋0)" ,
` event_code` TINYINT NOT NULL COMMENT '事件码: 1-拥堵TRAFFIC_CONGESTION,2-逆行REVERSE_DRIVE,3-溢出OVERFLOW,4-排队超限QUEUE_OVERRUN,5-路口锁死LOCKED,99-其他' ,
` event_status` TINYINT NOT NULL COMMENT '事件状态:1-交通事件,2-事件恢复' ,
` end_time` DATETIME COMMENT "事件结束时间" ,
` duration` SMALLINT DEFAULT "0" COMMENT "事件状态持续时间"
)
UNIQUE KEY ( ` time_stamp` , ` region_id` , ` intersection_number` , ` detector_nbr` , ` approach` , ` lane_nbr` , ` event_code` , ` event_status` )
DISTRIBUTED BY HASH ( ` region_id` , ` intersection_number` ) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1" ,
"enable_unique_key_merge_on_write" = "true"
) ;
Duplicate 模式
在某些多维分析场景下,数据既没有主键,也没有聚合需求。因此,引入 Duplicate 数据模型来满足这类需求 这种数据模型区别于 Aggregate和Unique模型。数据完全按照导入文件中的数据进行存储,不会有任何聚合 即使两行数据完全相同,也都会保留。 而在建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序 示例
DROP TABLE IF EXISTS signal. radar_fault_duplicate;
CREATE TABLE IF NOT EXISTS signal. radar_fault_duplicate
(
` time_stamp` DATETIME ( 3 ) NOT NULL COMMENT "雷达上报时间" ,
` region_id` INT NOT NULL COMMENT '路口所属区域编号' ,
` intersection_number` INT NOT NULL COMMENT "路口ID" ,
` detector_nbr` VARCHAR ( 16 ) NOT NULL COMMENT "设备编号" ,
` fault_type` VARCHAR ( 16 ) COMMENT '故障类型' ,
` fault_code` VARCHAR ( 16 ) COMMENT '故障码' ,
)
DUPLICATE KEY ( ` time_stamp` , ` region_id` , ` intersection_number` )
DISTRIBUTED BY HASH ( ` region_id` , ` intersection_number` ) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
) ;
数据划分
在 Doris 的存储引擎中,用户数据被水平划分为若干个数据分片(Tablet,也称作数据分桶) 每个 Tablet 包含若干数据行。各个 Tablet 之间的数据没有交集,并且在物理上是独立存储的 多个 Tablet 在逻辑上归属于不同的分区(Partition)。一个 Tablet 只属于一个 Partition。而一个 Partition 包含若干个 Tablet 因为 Tablet 在物理上是独立存储的,所以可以视为 Partition 在物理上也是独立。Tablet 是数据移动、复制等操作的最小物理存储单元 若干个 Partition 组成一个 Table。Partition 可以视为是逻辑上最小的管理单元。数据的导入与删除,都可以或仅能针对一个 Partition 进行 Doris 的建表是一个同步命令,SQL执行完成即返回结果,命令返回成功即表示建表成功
Partition
Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式 Range 是范围,列通常为时间列,以方便的管理新旧数据 List分区列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 数据类型,分区值为枚举值。只有当数据为目标分区枚举值其中之一时,才可以命中分区。 Partition 列可以指定一列或多列,分区列必须为 KEY 列 不论分区列是什么类型,在写分区值时,都需要加双引号。分区数量理论上没有上限 当不使用 Partition 建表时,系统会自动生成一个和表名同名的,全值范围的Partition。该 Partition 对用户不可见,并且不可删改 创建分区时不可添加范围重叠的分区 也可以仅使用一层分区。使用一层分区时,只支持 Bucket 划分
Bucket
Doris 支持两层的数据划分,第二层是 Bucket(Tablet),仅支持 Hash 的划分方式 如果使用了 Partition,则 DISTRIBUTED ...
语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。 分桶列可以是多列,Aggregate 和 Unique 模型必须为 Key 列,Duplicate 模型可以是 key 列和 value 列。分桶列可以和 Partition 列相同或不同。 分桶列的选择,是在 查询吞吐 和 查询并发 之间的一种权衡:
如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。 如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的IO影响较小(尤其当不同桶分布在不同磁盘上时),所以这种方式适合高并发的点查询场景。 AutoBucket: 根据数据量,计算分桶数。 对于分区表,可以根据历史分区的数据量、机器数、盘数,确定一个分桶。 分桶的数量理论上没有上限
创建库、表
创建一个数据库
create database demo;
创建数据表
use demo;
CREATE TABLE IF NOT EXISTS demo. example_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 "用户最小停留时间"
)
AGGREGATE KEY ( ` user_id` , ` date ` , ` city` , ` age` , ` sex` )
DISTRIBUTED BY HASH ( ` user_id` ) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
) ;