Doris
存储模型介绍
Aggregate模型: 聚合模型 按照明细写入数据 存储时按照规则会进行自动的聚合计算
优点: 适合聚合场景,而且聚合数据都是在数据摄入时计算好了,查询时减少了聚合计算的数据量
缺点: 聚合后的数据丢失了明细,无法进行明细检索
AggregationType: 表中的列按照是否设置了
AggregationType,分为 Key (维度列) 和 Value(指标列)。没有设置AggregationType的,如user_id、date、age… 等称为 Key,而设置了AggregationType的称为 Value。AggregationType四种聚合方式:导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的AggregationType进行聚合。- SUM:求和,多行的 Value 进行累加。
- REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value
- MAX:保留最大值
- MIN:保留最小值
数据聚合的过程:
- 每一批次数据导入的 ETL 阶段。该阶段会在每一批次导入的数据内部进行聚合
- 底层 BE 进行数据 Compaction 的阶段。该阶段,BE 会对已导入的不同批次的数据进行进一步的聚合
- 数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合
聚合模型的局限性:
- 数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生
- 建表语句
CREATE TABLE IF NOT EXISTS example_db.expamle_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`)... /* 省略 Partition 和 Distribution 信息 */
Uniq模型: 保证key的唯一性 相同的key时 除key列外的其他字段会覆盖更新
优点: 支持更新操作,一般的binlog数据在写入时,按照主键进行更新,doris表可以保持和mysql一致性
缺点: 更新会丢失历史明细数据,无法查看历史数据变化过程,只有终态
在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性,即如何获得 Primary Key 唯一性约束.
Uniq 模型完全可以用聚合模型中的 REPLACE 方式替代。其内部的实现方式和数据存储方式也完全一样
建标语句
CREATE TABLE IF NOT EXISTS example_db.expamle_tbl(`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`)... /* 省略 Partition 和 Distribution 信息 */
Duplicate模型: 存储明细数据, 适合即没有主键、也没有聚合需求, 数据写入什么形式, 就按照什么形式存储
- 优点: 原始数据存储,可以应对任何形式的分析
- 缺点: 占存储,查询效率低下(可以通过rollup索引或者物化视图提供更灵活的查询能力)
- 这种数据模型区别于 Aggregate 和 Uniq 模型。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。 而在建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序。(更贴切的名称应该为 “Sorted Column”,这里取名 “DUPLICATE KEY” 只是用以明确表示所用的数据模型。关于 “Sorted Column”的更多解释,可以参阅前缀索引小节)。在 DUPLICATE KEY 的选择上,我们建议适当的选择前 2-4 列就可以。
- ROLLUP
ROLLUP 在多维分析中是“上卷”的意思,即将数据按某种指定的粒度进行进一步聚合在 Doris 中,我们将用户通过建表语句创建出来的表称为 Base 表(Base Table)。Base 表中保存着按用户建表语句指定的方式存储的基础数据。在 Base 表之上,我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的,并且在物理上是**独立存储**的。ROLLUP 表的基本作用,在于在 Base 表的基础上,获得更粗粒度的聚合数据。
- ROLLUP
Aggregate模型 和Uniq模型
- 因为Uniq只是Aggregate模型的一个特例, 故此处只分析Aggregate模型
- 我们在Base表的基础上创建了RollUp表时 在查询时 针对某些查询 Rodis会自动命中RollUp表 从而减少查询的数据量
Duplicate模型
- 因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序,以命中前缀索引的作用
- 前缀索引和ROLLUP
- 因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序,以命中前缀索引的作用
基本概念 ``` 不同于传统的数据库设计,Doris 不支持在任意列上创建索引。Doris 这类 MPP 架构的 OLAP 数据库,通常都是通过提高并发,来处理大量数据的。 本质上,Doris 的数据存储在类似 SSTable(Sorted String Table)的数据结构中。该结构是一种有序的数据结构,可以按照指定的列进行排序存储。在这种数据结构上,以排序列作为条件进行查找,会非常的高效。
在 Aggregate、Uniq 和 Duplicate 三种数据模型中。底层的数据存储,是按照各自建表语句中,AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY 中指定的列进行排序存储的。
而前缀索引,即在排序的基础上,实现的一种根据给定前缀列,快速查询数据的索引方式。
我们将一行数据的前 36 个字节 作为这行数据的前缀索引。当遇到 VARCHAR 类型时,前缀索引会直接截断
-**ROLLUP调整前缀索引**- 因为建表时已经指定了列顺序,所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说,效率上可能无法满足需求。因此,我们可以通过创建 ROLLUP 来人为的调整列顺序- sql查询时 如果命中了rollup表的前缀索引, 会优先选择 ROLLUP 表,因为 ROLLUP 的前缀索引匹配度更高-**ROLLUP的几点说明**- ROLLUP 最根本的作用是提高某些查询的查询效率(无论是通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。因此 ROLLUP 的含义已经超出了 “上卷” 的范围。这也是为什么我们在源代码中,将其命名为 Materialized Index(物化索引)的原因。- ROLLUP 是附属于 Base 表的,可以看做是 Base 表的一种辅助数据结构。用户可以在 Base 表的基础上,创建或删除 ROLLUP,但是不能在查询中显式的指定查询某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。- ROLLUP 的数据是独立物理存储的。因此,创建的 ROLLUP 越多,占用的磁盘空间也就越大。同时对导入速度也会有影响(导入的ETL阶段会自动产生所有 ROLLUP 的数据),但是不会降低查询效率(只会更好)。- ROLLUP 的数据更新与 Base 表示完全同步的。用户无需关心这个问题。- ROLLUP 中列的聚合方式,与 Base 表完全相同。在创建 ROLLUP 无需指定,也不能修改。- 查询能否命中 ROLLUP 的一个必要条件(非充分条件)是,查询所涉及的**所有列**(包括 select list 和 where 中的查询条件列等)都存在于该 ROLLUP 的列中。否则,查询只能命中 Base 表。- 某些类型的查询(如 count(*))在任何条件下,都无法命中 ROLLUP。具体参见接下来的 **聚合模型的局限性** 一节。- 可以通过 `EXPLAIN your_sql;` 命令获得查询执行计划,在执行计划中,查看是否命中 ROLLUP。- 可以通过 `DESC tbl_name ALL;` 语句显示 Base 表和所有已创建完成的 ROLLUP。-**Aggregate和uniq模型的局限性**- 使用count(*)时会扫描全部的行。计算较复杂 可以增加count列 赋1-**数据模型的选择建议**<br />因为数据模型在建表时就已经确定,且**无法修改**。所以,选择一个合适的数据模型**非常重要**。1. Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。2. Uniq 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。3. Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。<a name="c63096db"></a>### Tablet & Partition-一个table 对应多个Partition Partition是逻辑上管理的最小单位-一个Partition 对应多个Tablet Tablet是物理上管理的最小单位-
在 Doris 的存储引擎中,用户数据被水平划分为若干个数据分片(Tablet,也称作数据分桶)。每个 Tablet 包含若干数据行。各个 Tablet 之间的数据没有交集,并且在物理上是独立存储的。
多个 Tablet 在逻辑上归属于不同的分区(Partition)。一个 Tablet 只属于一个 Partition。而一个 Partition 包含若干个 Tablet。因为 Tablet 在物理上是独立存储的,所以可以视为 Partition 在物理上也是独立。Tablet 是数据移动、复制等操作的最小物理存储单元。
若干个 Partition 组成一个 Table。Partition 可以视为是逻辑上最小的管理单元。数据的导入与删除,都可以或仅能针对一个 Partition 进行
-```sql-- Range PartitionCREATE 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=olapAGGREGATE 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 16PROPERTIES("replication_num" = "3","storage_medium" = "SSD","storage_cooldown_time" = "2018-01-01 12:00:00");-- List PartitionCREATE 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=olapAGGREGATE 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 16PROPERTIES("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 列。
定义列时,可参照如下建议:- Key 列必须在所有 Value 列之前。
- 尽量选择整型类型。因为整型类型的计算和查找比较效率远高于字符串。
- 对于不同长度的整型类型的选择原则,遵循 够用即可。
- 对于 VARCHAR 和 STRING 类型的长度,遵循 够用即可。
- 所有列的总字节长度(包括 Key 和 Value)不能超过 100KB
分区与分桶
Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。
第二层是 Bucket(Tablet),仅支持 Hash 的划分方式。也可以仅使用一层分区。使用一层分区时,只支持 Bucket 划分。
具体参考: http://doris.apache.org/master/zh-CN/getting-started/data-partition.html#数据划分-2
Partition
Partition 列可以指定一列或多列。分区类必须为 KEY 列。多列分区的使用方式在后面 多列分区 小结介绍。
不论分区列是什么类型,在写分区值时,都需要加双引号。
分区数量理论上没有上限。
当不使用 Partition 建表时,系统会自动生成一个和表名同名的,全值范围的 Partition。该 Partition 对用户不可见,并且不可删改
Range 分区
分区列通常为时间列,以方便的管理新旧数据。
Partition 支持通过
VALUES LESS THAN (...)仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区间。通过,也支持通过VALUES [...)指定同时指定上下界,生成一个左闭右开的区间。通过
VALUES [...)同时指定上下界比较容易理解。这里举例说明,当使用VALUES LESS THAN (...)语句进行分区的增删操作时,分区范围的变化情况:- 如上
expamle_range_tbl示例,当建表完成后,会自动生成如下3个分区:p201701: [MIN_VALUE, 2017-02-01)p201702: [2017-02-01, 2017-03-01)p201703: [2017-03-01, 2017-04-01)
- 如上
-
当我们增加一个分区 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,则分区结果如下:
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)。即如果导入的数据范围在这个空洞范围内,是无法导入的。
-
继续删除分区 p201702,分区结果如下:
p201701: [MIN_VALUE, 2017-02-01)p201705: [2017-04-01, 2017-06-01)空洞范围变为:[2017-02-01, 2017-04-01)
-
现在增加一个分区 p201702new VALUES LESS THAN (“2017-03-01”),分区结果如下:
p201701: [MIN_VALUE, 2017-02-01)p201702new: [2017-02-01, 2017-03-01)p201705: [2017-04-01, 2017-06-01)
可以看到空洞范围缩小为:[2017-03-01, 2017-04-01)
-
现在删除分区 p201701,并添加分区 p201612 VALUES LESS THAN (“2017-01-01”),分区结果如下:
p201612: [MIN_VALUE, 2017-01-01)p201702new: [2017-02-01, 2017-03-01)p201705: [2017-04-01, 2017-06-01)
即出现了一个新的空洞:[2017-01-01, 2017-02-01)
#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_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
- 如果使用了 Partition,则
DISTRIBUTED ...语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。 - 分桶列可以是多列,但必须为 Key 列。分桶列可以和 Partition 列相同或不同。
分桶列的选择,是在 查询吞吐 和 查询并发 之间的一种权衡:
- 如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。
- 如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的IO影响较小(尤其当不同桶分布在不同磁盘上时),所以这种方式适合高并发的点查询场景。
- 分桶的数量理论上没有上限。
- 如果使用了 Partition,则
关于 Partition 和 Bucket 的数量和数据量的建议。
- 一个表的 Tablet 总数量等于 (Partition num * Bucket num)。
- 一个表的 Tablet 数量,在不考虑扩容的情况下,推荐略多于整个集群的磁盘数量。
- 单个 Tablet 的数据量理论上没有上下界,但建议在 1G - 10G 的范围内。如果单个 Tablet 数据量过小,则数据的聚合效果不佳,且元数据管理压力大。如果数据量过大,则不利于副本的迁移、补齐,且会增加 Schema Change 或者 Rollup 操作失败重试的代价(这些操作失败重试的粒度是 Tablet)。
- 当 Tablet 的数据量原则和数量原则冲突时,建议优先考虑数据量原则。
- 在建表时,每个分区的 Bucket 数量统一指定。但是在动态增加分区时(
ADD PARTITION),可以单独指定新分区的 Bucket 数量。可以利用这个功能方便的应对数据缩小或膨胀。 - 一个 Partition 的 Bucket 数量一旦指定,不可更改。所以在确定 Bucket 数量时,需要预先考虑集群扩容的情况。比如当前只有 3 台 host,每台 host 有 1 块盘。如果 Bucket 的数量只设置为 3 或更小,那么后期即使再增加机器,也不能提高并发度。
- 举一些例子:假设在有10台BE,每台BE一块磁盘的情况下。如果一个表总大小为 500MB,则可以考虑4-8个分片。5GB:8-16个。50GB:32个。500GB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片。5TB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片。
注:表的数据量可以通过
show data命令查看,结果除以副本数,即表的数据量。
#多列分区
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 --> 无法导入
<a name="220f08a1"></a>##### [#](http://doris.apache.org/master/zh-CN/getting-started/data-partition.html#list-%E5%88%86%E5%8C%BA-2)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 ---> 无法导入
<a name="1450bef7"></a>### [#](http://doris.apache.org/master/zh-CN/getting-started/data-partition.html#properties)PROPERTIES在建表语句的最后 PROPERTIES 中,可以指定以下两个参数:1. replication_num- 每个 Tablet 的副本数量。默认为3,建议保持默认即可。在建表语句中,所有 Partition 中的 Tablet 副本数量统一指定。而在增加新分区时,可以单独指定新分区中 Tablet 的副本数量。- 副本数量可以在运行时修改。强烈建议保持奇数。- 最大副本数量取决于集群中独立 IP 的数量(注意不是 BE 数量)。Doris 中副本分布的原则是,不允许同一个 Tablet 的副本分布在同一台物理机上,而识别物理机即通过 IP。所以,即使在同一台物理机上部署了 3 个或更多 BE 实例,如果这些 BE 的 IP 相同,则依然只能设置副本数为 1。- 对于一些小,并且更新不频繁的维度表,可以考虑设置更多的副本数。这样在 Join 查询时,可以有更大的概率进行本地数据 Join。2. 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`。<a name="720caf21"></a>### [#](http://doris.apache.org/master/zh-CN/getting-started/data-partition.html#engine)ENGINE本示例中,ENGINE 的类型是 olap,即默认的 ENGINE 类型。在 Doris 中,只有这个 ENGINE 类型是由 Doris 负责数据管理和存储的。其他 ENGINE 类型,如 mysql、broker、es 等等,本质上只是对外部其他数据库或系统中的表的映射,以保证 Doris 可以读取这些数据。而 Doris 本身并不创建、管理和存储任何非 olap ENGINE 类型的表和数据。<a name="e2d9c2d2"></a>### [#](http://doris.apache.org/master/zh-CN/getting-started/data-partition.html#%E5%85%B6%E4%BB%96)其他
IF NOT EXISTS 表示如果没有创建过该表,则创建。注意这里只判断表名是否存在,而不会判断新建表结构是否与已存在的表结构相同。所以如果存在一个同名但不同构的表,该命令也会返回成功,但并不代表已经创建了新的表和新的结构。
```
#常见问题
#建表操作常见问题
如果在较长的建表语句中出现语法错误,可能会出现语法错误提示不全的现象。这里罗列可能的语法错误供手动纠错:
- 语法结构错误。请仔细阅读
HELP CREATE TABLE;,检查相关语法结构。 - 保留字。当用户自定义名称遇到保留字时,需要用反引号 `` 引起来。建议所有自定义名称使用这个符号引起来。
- 中文字符或全角字符。非 utf8 编码的中文字符,或隐藏的全角字符(空格,标点等)会导致语法错误。建议使用带有显示不可见字符的文本编辑器进行检查。
- 语法结构错误。请仔细阅读
Failed to create partition [xxx] . Timeout
Doris 建表是按照 Partition 粒度依次创建的。当一个 Partition 创建失败时,可能会报这个错误。即使不使用 Partition,当建表出现问题时,也会报Failed to create partition,因为如前文所述,Doris 会为没有指定 Partition 的表创建一个不可更改的默认的 Partition。
当遇到这个错误是,通常是 BE 在创建数据分片时遇到了问题。可以参照以下步骤排查:- 在 fe.log 中,查找对应时间点的
Failed to create partition日志。在该日志中,会出现一系列类似{10001-10010}字样的数字对。数字对的第一个数字表示 Backend ID,第二个数字表示 Tablet ID。如上这个数字对,表示 ID 为 10001 的 Backend 上,创建 ID 为 10010 的 Tablet 失败了。 - 前往对应 Backend 的 be.INFO 日志,查找对应时间段内,tablet id 相关的日志,可以找到错误信息。
以下罗列一些常见的 tablet 创建失败错误,包括但不限于:
- BE 没有收到相关 task,此时无法在 be.INFO 中找到 tablet id 相关日志。或者 BE 创建成功,但汇报失败。以上问题,请参阅 [部署与升级文档] 检查 FE 和 BE 的连通性。
- 预分配内存失败。可能是表中一行的字节长度超过了 100KB。
Too many open files。打开的文件句柄数超过了 Linux 系统限制。需修改 Linux 系统的句柄数限制。
- 在 fe.log 中,查找对应时间点的
tablet_create_timeout_second=xxx``max_create_table_timeout_second=xxx``tablet_create_timeout_second``max_create_table_timeout_second
- 建表命令长时间不返回结果。
Doris 的建表命令是同步命令。该命令的超时时间目前设置的比较简单,即(tablet num * replication num)秒。如果创建较多的数据分片,并且其中有分片创建失败,则可能导致等待较长超时后,才会返回错误。
正常情况下,建表语句会在几秒或十几秒内返回。如果超过一分钟,建议直接取消掉这个操作,前往 FE 或 BE 的日志查看相关错误。
RollUp与查询
在 Doris 里 Rollup 作为一份聚合物化视图,其在查询中可以起到两个作用:
- 索引
- 聚合数据(仅用于聚合模型,即aggregate key)
但是为了命中 Rollup 需要满足一定的条件,并且可以通过执行计划中 ScanNode 节点的 PreAggregation 的值来判断是否可以命中 Rollup,以及 Rollup 字段来判断命中的是哪一张 Rollup 表。
