1、数据模型
StarRocks 支持四种数据模型,在建表时,根据业务场景选准合适的数据模型,是最基本性能优化。
1.1. 明细模型 DISTRIBUTED Key
明细模型是默认的建表模型。如果在建表时未指定任何模型,默认创建的是明细类型的表。
明细模型并不会对数据做任何处理,DUPLICATE KEY
只用于定义排序键,相同 DUPLICATE KEY
的记录会同时存在。 所以推荐用来存储原始的日志明细数据。
注意:
- 在建表语句中,排序键必须定义在其他列之前。
- 排序键可以通过
DUPLICATE KEY
显式定义。如果未指定,则默认选择表的前三列作为排序键。
1.2. 聚合模型 AGGREGATE Key
建表时,支持定义排序键和指标列,并为指标列指定聚合函数。当多条数据具有相同的排序键时,指标列会进行聚合。
在分析统计和汇总数据时,聚合模型能够减少查询时所需要处理的数据,提升查询效率。
注意:
- 在建表语句中,排序键必须定义在其他列之前。
- 排序键可以通过
AGGREGATE KEY
显式定义。 - 指标列:通过在列名后指定聚合函数,定义该列为指标列。目前支持的常见聚合函数有:SUM、MAX、MIN、REPLACE、REPLACE_IF_NOT_NULL、HLL_UNION、BITMAP_UNION。
- 维度列:除指标列之外的列都是维度列,
AGGREGATE KEY
必需包含所有维度列,否则建表会失败。如果不通过 AGGREGATE KEY 显示定义排序键,则默认除指标列之外的列均为排序键。
1.3. 更新模型 Unique Key
建表时,支持定义主键和指标列,查询时返回主键相同的一组数据中的最新数据。
更新模型可以视为聚合模型的特殊情况,指标列指定的聚合函数为 REPLACE
,返回具有相同主键的一组数据中的最新数据。
注意:
- 在建表语句中,主键(排序键)必须定义在其他列之前。
- 主键通过
UNIQUE KEY
定义。
1.4. 主键模型 Primary Key
建表时,支持定义主键和指标列,查询时返回主键相同的一组数据中的最新数据。
- 在建表语句中,主键必须定义在其他列之前。
- 主键通过
PRIMARY KEY
定义。 - 主键必须满足唯一性约束,且列的值不会修改。
- 主键支持以下数据类型:BOOLEAN、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DATE、DATETIME、VARCHAR/STRING。并且不允许为 NULL。
分区列
和分桶列
必须在主键中。
更新模型、主键模型的区别
更新模型整体上采用了 Merge-On-Read 的策略。虽然写入时处理简单高效,但是查询时需要在线聚合多版本。
主键模型采用了 Delete+Insert 的策略,保证同一个主键下仅存在一条记录,这样就完全避免了 Merge 操作。具体实现方式如下:
- StarRocks 收到对某记录的更新操作时,会通过主键索引找到该条记录的位置,并对其标记为删除,再插入一条新的记录。相当于把 Update 改写为 Delete+Insert。
- StarRocks 收到对某记录的删除操作时,会通过主键索引找到该条记录的位置,对其标记为删除。
2. 排序键与前缀索引
在建表时,可以指定一个或多个列构成排序键 (Sort Key)。表中的行会根据排序键进行排序以后再落入磁盘存储。
因为数据是按照顺序存储的,在查询数据时,使用排序列指定过滤条件。有些情况下 StarRocks 不需要扫描全表即可快速找到需要处理的数据(例如二分法),降低搜索的复杂度,从而加速查询。
为了进一步加速查询,StarRocks 在排序键的基础上又引入了前缀索引 (Prefix Index)
。前缀索引是一种 稀疏索引
。表中每 1024 行数据构成一个逻辑数据块 (Data Block)。每个逻辑数据块在前缀索引表中存储一个索引项,索引项的长度不超过 36 字节,其内容为数据块中第一行数据的排序列组成的前缀,在查找前缀索引表时可以帮助确定该行数据所在逻辑数据块的起始行号。前缀索引的大小会比数据量少 1024 倍,因此会全量缓存在内存中,在实际查找的过程中可以有效加速查询。
2.1. 排序键
1. 各模型创建排序键
- 明细模型中,排序列就是通过 DUPLICATE KEY 关键字指定的列。
- 聚合模型中,排序列就是通过 AGGREGATE KEY 关键字指定的列。
- 更新模型中,排序列就是通过 UNIQUE KEY 关键字指定的列。
- 主键模型中,3.0 版本之后,主键表支持分别定义主键和排序键,通过 PRIMARY KEY 定义主键,可以再通过 ORDER BY 定义排序列。但3.0 之前的版本,只能通过 PRIMARY KEY 定义主键和排序键。
2. 创建排序键注意点
在定义排序列时,需要注意以下几点:
- 排序列必须从定义的第一列开始、并且是连续的。
- 在定义各列时,计划作为排序列的列必须定义在其他普通列之前。
- 排序列的顺序必须与表定义的列顺序一致。
如果是3.0 版本后的主键表,额外通过 ORDER BY 定义排序列则例外,无需遵守上述规则,可指定为任意列的排列组合。
3. 举例说明
例如,建表语句中声明要创建 site_id、city_code、user_id 和 pv 四列。这种情况下,正确的排序列组合和错误的排序列组合举例如下:
正确的排序列
- site_id 和 city_code
- site_id、city_code 和 user_id
错误的排序列
- city_code 和 site_id
- city_code 和 user_id
- site_id、city_code 和 pv
因为数据是按照排序键顺序存储的,当基于排序键进行查询时,通过二分法会更容易定位到数据。
假设排序键是 city_code
、site_id
。那么查询条件中包含 city_code
、site_id
,或者只有city_code
,可以达到查询优化效果,但如果只有site_id
则不行。
当然排序键基于查询的优化不主要取决于此,主要是前缀索引。因为就算排序后的数据可以基于二分法定位,但都需要将排序列的全部数据都先载入内存。而通过前缀索引可以定位到数据所在的逻辑数据块,每个数据块最大只有1024行数据,然后针对这个数据块内的数据做二分法即可。而前缀索引本身的数据量只占实际数据的1/1024。
2.2. 前缀索引
首先说明,前缀索引不需要我们手动创建,在建表时随着我们指定了排序键,StarRocks就默认创建了前缀索引。
因为前缀索引是基于排序列自动创建的,如果指定的排序列非常多,前缀索引也会占用大量内存。为了避免这种情况,StarRocks 对前缀索引做了如下限制:
1. 前缀索引限制
- 前缀索引列的数量不能超过 3。
- 前缀索引项的长度不能超过 36 字节。
- 前缀索引中不能包含 FLOAT 或 DOUBLE 类型的列。因为它们是二进制格式存储的,前缀并不能用来表示数值的大小或者范围。
- 前缀索引中 VARCHAR 类型的列只能出现一次,并且处在末尾位置。因为列存储字符串长度可变,无法固定匹配的排序列,也就无法有效基于前缀匹配数据。
2. 举例说明
假设某表中排序列依次为:
- user_id:BIGINT,8 Byte
- age:INT,4 Byte
- address:VARCHAR(100),最大100个字符,字节数根据存储数据符合及编码定
那么建表后创建的前缀索引是:user_id(8) + age(4) + address(截取前24个字节)
2.3. 总结
在建表时如何选择合适的排序列,以及排序键中列的顺序,都需要仔细思考。有以下建议:
- 经常作为查询条件的列,建议选为排序列。
- 当排序键涉及多个列的时候,建议把区分度高(指取值个数较多、且持续增加的列)、且经常查询的列放在前面。
- 排序键不应该包含过多的列。选择很多排序列并不有助于提升查询性能,因为前缀索引长度不能超过36个字节。而且会增大排序的开销,进而增加数据导入的开销。
3. Bloom Filter 索引
如果希望快速查询基数较高的列,那么建议在这个列创建Bloom filter(布隆过滤器)索引。
3.1. 原理
底层实现时,StarRocks为每个数据块中的索引列生成 Bloom Filter。每个Bloom Filter包含所有该数据块中该列的值。
那么在查询时,先通过每个数据库的 Bloom filter 索引过滤:
- 如果某个Bloom filter 索引判断不存在目标数据,那 StarRocks 会跳过该数据块文件,从而提高查询效率。
- 如果某个Bloom filter 索引判断存在目标数据,因为布隆过滤器的假阳性概率,StarRocks 会再读取该数据块文件,然后在遍历该数据块数据,确认目标数据是否存在。
1. 布隆过滤器的假阳性概率
Bloom filter 索引有一定的误判率,也称为假阳性概率,即判断某行中包含目标数据,而实际上该行并不包含目标数据的概率。但如果判断某行中不包含目标数据,就一定不包含目标数据。
2. 高基数列
基于布隆过滤器的特点,如果Bloom filter 索引判断存在目标数据的话,还需要将完整数据块加载进内存,做进一步判断,如果索引判断不存在,则可以直接跳过去。
如果索引列的基数不高的话,在索引匹配阶段,会有大量的Bloom filter 索引判断存在目标数据。无法充分发挥Bloom filter 索引的优化性能。
如果索引列的基数很高的话,在索引匹配阶段,会有大量的Bloom filter 索引判断不存在目标数据,而被跳过。只需要进一步加载很少一部分Bloom filter的数据块就行。
3.2. 使用说明
主键模型
和明细模型
中所有列都可以创建 Bloom filter 索引;聚合模型
和更新模型
中,只有维度列(即 Key 列)支持创建 Bloom filter 索引。- 不支持为 TINYINT、FLOAT、DOUBLE 和 DECIMAL 类型的列创建 Bloom filter 索引。
- Bloom filter 索引只能提高包含
in
和=
过滤条件的查询效率,例如Select xxx from table where xxx in ()
和Select xxx from table where column = xxx
。
3.3. 创建/删除索引
1. 建表时创建
建表时,通过在 PROPERTIES 中指定 bloom_filter_columns 来创建索引。例如,如下语句为表 table1 的 k1 和 k2 列创建 Bloom filter 索引。多个索引列之间需用逗号 (,) 隔开。
CREATE TABLE table1
(
k1 BIGINT,
k2 LARGEINT,
v1 VARCHAR(2048) REPLACE,
v2 SMALLINT DEFAULT "10"
)
ENGINE = olap
PRIMARY KEY(k1, k2)
DISTRIBUTED BY HASH (k1, k2) BUCKETS 10
PROPERTIES("bloom_filter_columns" = "k1,k2");
2. 修改
可以使用 ALTER TABLE 语句来增加,减少和删除索引。
(1) 如下语句增加了一个 Bloom filter 索引列 v1。
ALTER TABLE table1 SET ("bloom_filter_columns" = "k1,k2,v1");
(2) 如下语句减少了一个 Bloom filter 索引列 k2。
ALTER TABLE table1 SET ("bloom_filter_columns" = "k1");
(3) 如下语句删除了 table1 的所有 Bloom filter 索引。
ALTER TABLE table1 SET ("bloom_filter_columns" = "");
4. BitMap 索引
如果希望快速查询基数较低的列(值大量重复,例如 ENUM 类型的列),那么建议在这个列创建BitMap(位图)索引。
4.1. 原理
BitMap 即为一个 bit 数组,一个 bit 的取值有两种:0 或 1。
针对某一列创建BitMap索引时,就会给该列的每一种值创建一个BitMap结构。
BitMap的长度等于表数据的长度,表数据是有顺序的。假设针对某列创建BitMap索引,该列有值为“A”,那么在值“A”对应的BitMap结构中,如果表中第n行数据的列值也为A,那么BitMap中第n位值为1,否则就为0.
1. 举例说明
假设有一张用户表(User),其中包含两列:国家(Country)和用户ID(UserID)。考虑到国家的数量相对于用户数来说通常很少,因此Country列具有低基数特点,适合创建BitMap索引。
我们为Country列创建BitMap索引后,假设有如下数据:
UserID Country
1 USA
2 Canada
3 USA
4 Mexico
对于这个例子,BitMap索引将创建如下:
- USA的BitMap: 1010
- Canada的BitMap: 0100
- Mexico的BitMap: 0001
每个位图有4位,分别代表UserID为1到4的用户是否来自该国家。
当我们需要查询所有来自"USA"的用户时:
- StarRocks会查找对应于"USA"的位图。
- 位图"1010"表明,UserID为1和3的用户来自"USA"。
- StarRocks将只检索UserID为1和3的行,而不需要检查UserID为2和4的行。
通过使用BitMap索引,StarRocks能够快速过滤掉不匹配的行,大大减少了查询所需的时间和资源消耗。特别是在涉及联合查询或者多条件查询时,通过对多个BitMap索引进行逻辑 "AND
"、"OR
"操作,StarRocks可以有效地定位满足所有条件的数据行,提高查询效率。
倒排索引
倒排索引(Inverted Index):是一种数据库索引类型,它用于存储一个单词或者一个字段值到包含它的文档或记录的映射。
很明显 BitMap索引就是一种倒排索引,不过不像es对文本进行分词再与记录映射,StarRocks是将索引列完整的值做映射。
4.2. 使用说明
- Bitmap 索引适用于可使用等值条件 (
=
) 查询或[NOT] IN
范围查询的列。 主键模型
和明细模型
中所有列都可以创建 Bitmap 索引;聚合模型
和更新模型中
,只有维度列(即 Key 列)支持创建 bitmap 索引。- 不支持为 FLOAT、DOUBLE、BOOLEAN 和 DECIMAL 类型的列创建 Bitmap 索引。
4.3. 创建/删除索引
- 建表时创建
建表时,通过在 PROPERTIES 中指定 bloom_filter_columns 来创建索引。例如,如下语句为表 table1 的 k1 和 k2 列创建 Bloom filter 索引。多个索引列之间需用逗号 (,) 隔开。
CREATE TABLE d0.table_hash
(
k1 TINYINT,
k2 DECIMAL(10, 2) DEFAULT "10.5",
v1 CHAR(10) REPLACE,
v2 INT SUM,
INDEX index_name (column_name) [USING BITMAP] [COMMENT '']
)
ENGINE = olap
AGGREGATE KEY(k1, k2)
DISTRIBUTED BY HASH(k1) BUCKETS 10
PROPERTIES ("storage_type" = "column");
参数说明:
- index_name:Bitmap 索引名称。 必须由字母(a-z或A-Z)、数字(0-9)或下划线(_)组成,且只能以字母开头。总长度不能超过 64 个字符。在同一张表中不能创建名称相同的索引。
- column_name:创建 Bitmap 索引的列名。
- COMMENT:非必填,索引备注。
- 单独创建索引
CREATE INDEX index_name ON table_name (column_name) [USING BITMAP] [COMMENT'']
参数说明:
- index_name:Bitmap 索引名称。 必须由字母(a-z或A-Z)、数字(0-9)或下划线(_)组成,且只能以字母开头。总长度不能超过 64 个字符。在同一张表中不能创建名称相同的索引。
- column_name:创建 Bitmap 索引的列名。执行一次该语句只能为某一列创建索引,且同一列只能创建一个索引。
- table_name:表名。
- COMMENT:非必填,索引备注。
- 删除索引
删除指定表的 Bitmap 索引。
DROP INDEX index_name ON [db_name.]table_name;
- 查看索引
查看索引,当前仅支持查看指定表的所有 Bitmap 索引。创建 bitmap 索引为异步过程,使用该语句只能查看到已经创建完成的索引
SHOW INDEX FROM [db_name.]table_name [FROM db_name]
或
SHOW KEY FROM [db_name.]table_name [FROM db_name]
5. 分区、分桶
5.1. 分区、分桶概念
5.1.1. 分区
分区用于将数据划分成不同的区间。分区的主要作用是将一张表按照分区键拆分成不同的管理单元,针对每一个管理单元选择相应的存储策略,比如副本数、分桶数、冷热策略和存储介质等。
StarRocks 支持在一个集群内使用多种存储介质,您可以将新数据所在分区放在 SSD 盘上,利用 SSD 优秀的随机读写性能来提高查询性能,将旧数据存放在 SATA 盘上,以节省数据存储的成本。
业务系统中⼀般会选择根据时间进行分区,以优化大量删除过期数据带来的性能问题,同时也方便冷热数据分级存储。
分区单位的选择,需要综合考虑数据量、查询特点、数据管理粒度等因素。
5.1.2. 分桶
分区的下一级是分桶,StarRocks 采⽤ Hash 算法作为分桶算法。
在同一分区内,分桶键哈希值相同的数据形成 Tablet,Tablet 以多副本冗余的形式存储,是数据均衡和恢复的最⼩单位。Tablet 的副本由一个单独的本地存储引擎管理,数据导入和查询最终都下沉到所涉及的 Tablet 副本上。
建表时,必须指定分桶键。
5.1.3. 分区、分桶搭配
基于分区、分桶的搭配,常见有2种数据分布方式。
- Hash 数据分布方式
一张表为一个分区,分区按照分桶键和分桶数量进一步进行数据划分。
如下表,只有一个分区,但有10个分桶,数据基于site_id的HASH值,分布在这10个分桶内。只要保证site_id的值是均匀的,就能保证数据是均匀分布在不同分桶内。
CREATE TABLE site_access(
site_id INT DEFAULT '10',
city_code SMALLINT,
user_name VARCHAR(32) DEFAULT '',
pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(site_id, city_code, user_name)
DISTRIBUTED BY HASH(site_id) BUCKETS 10;
- Range+Hash 数据分布方式
一张表拆分成多个分区,每个分区按照分桶键和分桶数量进一步进行数据划分。
如下表中,分区键为event_day,基于时间范围生成3个分区,对应不同时间范围的数据落在不同的分区内。
在每个分区内,再基于site_id的HASH值,将数据分布在10个分桶内。因此该表数据一共分布在30个桶内。
CREATE TABLE site_access(
event_day DATE,
site_id INT DEFAULT '10',
city_code VARCHAR(100),
user_name VARCHAR(32) DEFAULT '',
pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(event_day, site_id, city_code, user_name)
PARTITION BY RANGE(event_day)
(
PARTITION p1 VALUES LESS THAN ("2020-01-31"),
PARTITION p2 VALUES LESS THAN ("2020-02-29"),
PARTITION p3 VALUES LESS THAN ("2020-03-31")
)
DISTRIBUTED BY HASH(site_id) BUCKETS 10;
5.2. 分区
创建分区的方式简单可分为手动创建分区
和动态创建分区
,最常见的是动态创建分区。
目前支持分区键的数据类型为日期类型
和整数类型
。
5.2.1. 手动创建分区
在手动创建分区又可分为单个创建分区
和批量创建分区
。
1. 单个创建分区
如下,需要我们指定每个分区的实际范围。
CREATE TABLE site_access(
event_day DATE,
site_id INT DEFAULT '10',
city_code VARCHAR(100),
user_name VARCHAR(32) DEFAULT '',
pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(event_day, site_id, city_code, user_name)
PARTITION BY RANGE(event_day)
(
PARTITION p1 VALUES LESS THAN ("2020-01-31"),
PARTITION p2 VALUES LESS THAN ("2020-02-29"),
PARTITION p3 VALUES LESS THAN ("2020-03-31")
)
DISTRIBUTED BY HASH(site_id) BUCKETS 10;
短板:某些场景,如果我们需要创建分区的数量比较多,就不适合挨个指定每个分区的范围了,就可以用到下面的批量创建分区。
2. 批量创建分区
如下,我们指定从 "2021-01-01" 到 "2021-02-01" 每隔一天创建一个分区,只要就一次性创建了31个分区。
CREATE TABLE site_access (
datekey DATE,
site_id INT,
city_code SMALLINT,
user_name VARCHAR(32),
pv BIGINT DEFAULT '0'
)
ENGINE=olap
DUPLICATE KEY(datekey, site_id, city_code, user_name)
PARTITION BY RANGE (datekey) (
START ("2021-01-01") END ("2021-02-01") EVERY (INTERVAL 1 DAY)
)
DISTRIBUTED BY HASH(site_id) BUCKETS 10;
短板:上述手动创建分区的方式,都是基于固定范围创建时间。通常我们的表是不断有数据写入的。就算我们提前创建1年的分区,那么1年后能否自动创建分区呢?之前老的分区以及里面的历史数据是否可以自动删除呢?动态创建分区可以解决这个问题。
5.2.2. 动态创建分区
CREATE TABLE site_access(
event_day DATE,
site_id INT DEFAULT '10',
city_code VARCHAR(100),
user_name VARCHAR(32) DEFAULT '',
pv BIGINT DEFAULT '0'
)
DUPLICATE KEY(event_day, site_id, city_code, user_name)
PARTITION BY RANGE(event_day)(
PARTITION p20200321 VALUES LESS THAN ("2020-03-22"),
PARTITION p20200322 VALUES LESS THAN ("2020-03-23"),
PARTITION p20200323 VALUES LESS THAN ("2020-03-24")
)
DISTRIBUTED BY HASH(event_day, site_id) BUCKETS 32
PROPERTIES(
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-3",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "32",
"dynamic_partition.history_partition_num" = "0"
);
上面建表sql中,先手动预创建3个分区,PROPERTIES
中 dynamic_partition.enable
前缀的配置则是配置动态分区,列几个常见配置项:
dynamic_partition.enable
:开启动态分区特性,取值为 true(默认)或 false。dynamic_partition.time_unit
:必填,动态分区的时间粒度,取值为 HOUR、DAY、WEEK、MONTH 或 YEAR。例如:为MONTH时,动态创建的分区名后缀格式为 yyyyMM,每个月创建一个分区。dynamic_partition.time_zone
:动态分区的时区。默认与系统时区一致。dynamic_partition.start
:保留的动态分区的起始偏移,取值范围为负整数。根据dynamic_partition.time_unit
属性的不同,以当天(周/月)为基准,分区范围在此偏移之前的分区将会被删除。比如设置为-3,并且dynamic_partition.time_unit为day,则表示 3 天前的分区会被删掉。
如果不填写,则默认为 Integer.MIN_VALUE,即 -2147483648,表示不删除历史分区。dynamic_partition.end
:必填,提前创建的分区数量,取值范围为正整数。根据 dynamic_partition.time_unit 属性的不同,以当天(周/月)为基准,提前创建对应范围的分区。dynamic_partition.prefix
:动态分区的前缀名,默认值为 p。dynamic_partition.buckets
:动态分区的分桶数量。默认与 BUCKETS 保留字指定的分桶数量、或者 StarRocks 自动设置的分桶数量保持一致。dynamic_partition.replication_num
:在动态创建的分区中,每个 tablet 副本的数量。默认值与建表时配置的副本数量相同。
动态分区相关 FE 配置项
ynamic_partition_check_interval_seconds
:FE 配置项,动态分区检查的时间周期,默认为 600,单位为 s,即每10分钟检查一次分区情况是否满足 PROPERTIES
中动态分区属性,如不满足,则会自动 创建
和 删除
分区。
5.2.3. 其他命令
手动增加分区
如下示例中,在 site_access 表添加新的分区,用于存储新月份的数据,并且调整分桶数量为 20:
ALTER TABLE site_access
ADD PARTITION p4 VALUES LESS THAN ("2020-04-30")
DISTRIBUTED BY HASH(site_id) BUCKETS 20;
手动删除分区
执行如下语句,删除 site_access 表中分区 p1 及数据:
说明:分区中的数据不会立即删除,会在 Trash 中保留一段时间(默认为一天)。如果误删分区,可以通过 RECOVER 命令恢复分区及数据。
ALTER TABLE site_access
DROP PARTITION p1;
手动恢复分区
执行如下语句,恢复 site_access 表中分区 p1 及数据:
RECOVER PARTITION p1 FROM site_access;
查看表当前的分区情况
SHOW PARTITIONS FROM [db_name.]table_name
查看表动态分区属性
SHOW CREATE TABLE [db_name.]table_name
修改表的动态分区属性
执行 ALTER TABLE,修改动态分区的属性,例如暂停或者开启动态分区特性。
ALTER TABLE [db_name.]table_name SET("dynamic_partition.enable"="false");
ALTER TABLE [db_name.]table_name SET("dynamic_partition.enable"="true");
5.3. 分桶
5.3.1. 选择分桶键
对每个分区的数据,StarRocks 会根据分桶键和分桶数量进行哈希分桶。
分桶键的设置,一方面应该将数据均匀分布开,避免数据倾斜情况严重;另一方面如果查询简单,可以利用分桶优化查询。
优化查询
如果查询比较简单,建议选择经常作为查询条件的列为分桶键,提高查询效率。
假设某表常见查询条件包含:WHERE code={code}
,有10个分桶,如果将 code
作为分桶键。那么常见的查询场景中会过滤掉9个分桶,只需要扫描1个分桶的数据即可。
前提是,code
作为分桶键不会造成太大的数据倾斜,如果极端情况下基于“二八原则”,常用code
所在分桶内的数据量或访问量,远高于其他几个桶之和,那查询的性能也没有多大的提高。
避免数据倾斜
倘若查询的场景比较多,查询条件复杂,大多数场景无法通过某列轻松过滤掉很多数据,那么尽量选择高基数的列作为分桶键,保证数据在各个分桶中尽量均衡,提高集群资源利用率。
要结合实际业务场景,除了避免存储数据的倾斜,还要避免查询数据的倾斜,导致系统局部的性能瓶颈。
如果单列无法保证数据均匀分散,可以使用多个列作为数据的分桶键,但是建议不超过 3 个列。
5.3.2. 设置分桶
注意
- 建表时,如果使用哈希分桶,则必须指定分桶键。
- 组成分桶键的列仅支持整型、DECIMAL、DATE/DATETIME、CHAR/VARCHAR/STRING 数据类型。
- 分桶键指定后不支持修改。
- 组成分桶键的列,该列的值不能够更新。
5.3.2.1. 手动创建分桶
建表时设置
CREATE TABLE site_access
(
site_id INT DEFAULT '10',
city_code SMALLINT,
user_name VARCHAR(32) DEFAULT '',
pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(site_id, city_code, user_name)
DISTRIBUTED BY HASH(site_id,city_code) BUCKETS 10;
如上,在建表时通过 BUCKETS
指定每个分区的分桶数量。
新增分区时设置
已创建分区的分桶数量是不能修改的。
但是在新增分区的时,可以再为新的分区设定分桶数量。
ALTER TABLE <table_name> ADD PARTITION <partition_name>
[DISTRIBUTED BY HASH (k1[,k2 ...]) [BUCKETS num]];
5.3.2.2. 自动创建分桶
自 2.5.7 版本起, StarRocks 能够自动设置分桶数量。如果需要启用该功能,则您需要执行 ADMIN SET FRONTEND CONFIG ("enable_auto_tablet_distribution" = "true");
以开启该 FE 动态参数。
假设 BE 数量为 X,StarRocks 推断分桶数量的策略如下:
X <= 12 tablet_num = 2X
X <= 24 tablet_num = 1.5X
X <= 36 tablet_num = 36
X > 36 tablet_num = min(X, 48)
此时建表就不需要再设置分桶数量:
CREATE TABLE site_access(
site_id INT DEFAULT '10',
city_code SMALLINT,
user_name VARCHAR(32) DEFAULT '',
pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(site_id, city_code, user_name)
DISTRIBUTED BY HASH(site_id,city_code); --无需手动设置分桶数量
5.4. 分区、分桶建议
对于 StarRocks 而言,分区和分桶的选择是非常关键的。在建表时选择合理的分区键和分桶键,可以有效提高集群整体性能。因此建议在选择分区键和分桶键时,根据业务情况进行调整。
- 数据倾斜:如果业务场景中单独采用倾斜度大的列做分桶,很大程度会导致访问数据倾斜,那么建议采用多列组合的方式进行数据分桶。
- 高并发:分区和分桶应该尽量覆盖查询语句所带的条件,这样可以有效减少扫描数据,提高并发。
- 高吞吐:尽量把数据打散,让集群以更高的并发扫描数据,完成相应计算。
- 元数据管理:Tablet 过多会增加 FE/BE 的元数据管理和调度的资源消耗。
每个 Tablet 的大小一般控制在 1 GB ~ 10 GB。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。