在数据库技术的发展历程中,随着数据量的不断增长和业务需求的日益复杂,如何高效地存储、查询和处理数据成为了关键挑战。OceanBase作为一款高性能、高可用的分布式关系数据库,通过其独特的分区机制,为这一挑战提供了有力的解决方案。
分区,作为OceanBase数据库架构中的核心概念,是实现数据高效管理和高性能查询的关键。在OceanBase中,分区不仅仅是对数据的简单切分,更是一种智能化的数据管理策略。通过将数据水平拆分成多个物理上独立的单位,并结合多副本技术,OceanBase确保了数据的高可用性、持久性和容错性。
在本文中,我们将深入探讨OceanBase的分区机制,包括其设计理念、实现方式以及在实际应用中的优势和效果。通过了解OceanBase的分区,读者将能够更好地理解这款数据库如何为企业提供稳定、可靠的数据服务,满足现代业务对于数据存储和处理的严苛要求。
现在,让我们一同揭开OceanBase分区机制的神秘面纱,探索其背后的技术原理和实践应用。
在数据库管理系统中,分区是一种将数据水平拆分成多个较小的、更易于管理的部分的技术。这个概念在多个数据库系统中都有应用,但具体的实现和细节可能因系统而异。以下是对分区的详细解释,特别是在OceanBase中的实现:
分区在数据库管理系统中是一个重要的概念和技术,它有助于提高系统的性能、可用性和可维护性。在OceanBase中,分区以物理副本组的形式存在,默认采用三副本策略,为分布式数据库环境提供了强大的支持和保障。
在 MySQL 中,如果让我们在分区表和分库分表之间做选择,肯定很多人会毫不犹豫的选择分库分表,因为分区表虽然底层拆封出了多个物理文件,但是很多的操作其实还是表级,比如DDL,当表切换过程中,锁表影响的是所有分区。而分库分表只会影响部分表;第二就是分区表的负载其实还是集中在独立的实例上,并不能够做打散,而且当对应节点挂掉以后,所有分区都会收到影响,只能依赖后续的高可用。
而 OceanBase 因为本身是分布式数据库,所以它的分区其实更像 MySQL 的分库分表,因为底层通过将分区作为分片副本打散到不同的实例中,对于上层业务来说,并不需要关注底层分区的分布,而底层分区可以通过打散,来实现存储以及负载的均衡,并且某个实例(存在分区leader)宕机,也不会影响其他分区的读写。
所以分区表在分布式数据库中,有如下优势:
当然,分区表其实也是有隐患的,尤其是当分区使用不合理的时候,那么不仅有可能导致性能下降,甚至导致业务异常。比如 TP 的业务并且基本只会读取当天的数据,创建了天或者月级别的range分区,那么就会导致所有的请求集中到一个分区,出现热点问题。
所以创建合适的分区非常重要。
OceanBase 数据库的基本分区策略包括范围(Range/Range Columns)分区、列表(List/List Columns)分区、哈希(Hash)/Key 分区以及它们之间的组合。
Range 分区是按照某个连续的范围来划分数据区间,每个分区都包含分区表达式值位于给定范围内的行。常用于按年、月或日等时间维度进行分区。
特点:
根据分区键值的范围把数据行存储到表的不同分区中 多个分区的范围是连续的但不重叠。 默认情况下使用VALUES LESS THAN属性,每个分区不包括指定的那个值
适用场景 定期按分区范围清理历史数据 并发不高并且请求范围集中 范围查询
需要注意
常用的Range定义有下面几种:
直接根据字段范围:PARTITION BY RANGE(store_id)
根据时间年份:PARTITION BY RANGE ( YEAR(purchased))
根据时间戳:PARTITION BY RANGE(UNIX_TIMESTAMP(report_updated))
根据天:PARTITION BY RANGE(TO_DAYS(order_date))
根据时间字段范围:PARTITION BY RANGE COLUMNS(joined)
创建 Range 分区表
CREATE TABLE r (
id INT NOT NULL,
ctime DATE NOT NULL DEFAULT '2000-12-31'
)
PARTITION BY RANGE ( YEAR(ctime) ) (
PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1996),
PARTITION p2 VALUES LESS THAN (2001)
);
创建 Range COLUMNS 分区表
CREATE TABLE rc(
ctime DATE NOT NULL
)
PARTITION BY RANGE COLUMNS(ctime) (
PARTITION p0 VALUES LESS THAN ('1960-01-01'),
PARTITION p1 VALUES LESS THAN ('1970-01-01'),
PARTITION p2 VALUES LESS THAN ('1980-01-01'),
PARTITION p3 VALUES LESS THAN ('1990-01-01')
);
现有表创建 Range 分区
ALTER TABLE r PARTITION BY RANGE ( YEAR(ctime) ) (
PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1996),
PARTITION p2 VALUES LESS THAN (2001)
);
查询分区数据
select * from r partition(p0);
增加分区
ALTER TABLE r ADD PARTITION (PARTITION p3 VALUES LESS THAN(2006));
新增 MAXVALUE 分区
alter table r ADD PARTITION(PARTITION p4 VALUES less than (MAXVALUE));
清空分区
alter table r truncate partition p0;
删除分区
alter table r drop partition p0;
故名思义,List分区是根据给定的值列表将表进行分区,每个分区对应一个列表中的值。它跟range分区有些类似,每个分区都必须显式定义。
特点:
跟range分区有些类似,各分区的列表值不能重复,但是 List 分区数据不需要连续。
适用场景:
定期清理分区内的历史数据 并发不高并且请求范围集中
注意:
创建 List 分区表
CREATE TABLE l (
id INT NOT NULL,
store_id INT
)
PARTITION BY LIST(store_id) (
PARTITION p0 VALUES IN (3,5),
PARTITION p1 VALUES IN (1,2),
PARTITION p2 VALUES IN (4,6)
);
创建 List Columns 分区表
CREATE TABLE lc (
id INT NOT NULL,
store_id varchar(10)
)
PARTITION BY LIST COLUMNS(store_id) (
PARTITION p0 VALUES IN ("3","5"),
PARTITION p1 VALUES IN ("1","2"),
PARTITION p2 VALUES IN ("4","6")
);
现有表创建 List 分区
alter table l PARTITION BY LIST(store_id) (
PARTITION p0 VALUES IN (3,5),
PARTITION p1 VALUES IN (1,2),
PARTITION p2 VALUES IN (4,6)
);
新增分区
alter table l ADD PARTITION(PARTITION p3 VALUES IN (7,8));
新增 DEFAULT 分区
alter table l ADD PARTITION(PARTITION p3 VALUES IN (DEFAULT));
其他操作等同。
Hash 分区是数据库根据用户指定的分区键的哈希算法将行映射到分区,它跟 Range、List 不同,不再需要指定列值存储在哪个分区,这种方式一般情况下会将数据打散的更加均衡。
常规的 HASH 分区非常的简便,通过取模(N = MOD(expr, num))的方式可以让数据更加平均的分布每一个分区。比如4个分区,101会落在P1分区,因为 MOD( 101 , 4 ) = 1。
特点:
HASH 分区通常能消除热点查询,可以充分利用每台机器的资源。
适用场景:
注意:
1、如果业务有大量的范围查询,那么可能会造成大量的分区扫描,此时分区只会起到反效果。
2、HASH分区的键值必须是一个INT类型的值,或是通过函数可以转为INT类型
创建 Hash 分区表
CREATE TABLE h (
id INT NOT NULL,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
已有表创建 Hash 分区
alter table h PARTITION BY HASH(store_id) PARTITIONS 4;
KEY分区其实跟HASH分区差不多,不同点如下:
创建 Key 分区
默认不指定列,以主键或者唯一键自动分区
CREATE TABLE k (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;
指定列创建
CREATE TABLE k2 (
id INT NOT NULL,
store_id varchar(10)
)
PARTITION BY KEY(`id`,`store_id`)
PARTITIONS 2;
二级分区是指在分区表中每个一级分区的基础上,再做一层分区。二级分区和一级分区可以是同一个列,也可以是不同的列。可以实现在一级分区的基础上二次打散的效果。
对于模板化二级分区表来说,定义二级分区后,每个二级分区的命名规则为
(
subpart_name)。
例如:p0sp1。
创建二级分区表
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) )
SUBPARTITIONS 2 (
PARTITION p0 VALUES LESS THAN (1990),
PARTITION p1 VALUES LESS THAN (2000),
PARTITION p2 VALUES LESS THAN MAXVALUE
);
查询二级分区数据(二级分区创建表达式是 to_days)
obclient [test]> select * from ts partition(p0);
+------+------------+
| id | purchased |
+------+------------+
| 1 | 1980-01-20 |
| 3 | 1980-01-22 |
| 2 | 1980-01-21 |
+------+------------+
3 rows in set (0.025 sec)
obclient [test]> select * from ts partition(p0sp0);
+------+------------+
| id | purchased |
+------+------------+
| 2 | 1980-01-21 |
+------+------------+
1 row in set (0.014 sec)
obclient [test]> select * from ts partition(p0sp1);
+------+------------+
| id | purchased |
+------+------------+
| 1 | 1980-01-20 |
| 3 | 1980-01-22 |
+------+------------+
2 rows in set (0.006 sec)
查询分区明细
SELECT table_name,partition_name,subpartition_name FROM information_schema.partitions;
分区的限制以及常见问题
限制
常见问题
A PRIMARY KEY must include all columns in the table’s partitioning function:分区键必须是主键或者唯一键或者其中的部分列,主键或者唯一键必须包含分区键,否则会创建失败。
比如下面两个例子都会失败
案例 1
CREATE TABLE t1 (s1 CHAR(32) PRIMARY KEY, s2 CHAR(32) ) PARTITION BY KEY(s2) PARTITIONS 4;
案例 2
CREATE TABLE t1 (s1 CHAR(32) PRIMARY KEY, s2 CHAR(32) ) PARTITION BY KEY(s2,s1) PARTITIONS 4;
当主键为 (s1,s2)这样的组合主键时,上面的两个sql可以执行成功。
简单来说,这个是索引组织表的限制。之所以对索引组织表有这样的限制,还是基于性能考虑。
假设分区键和主键是两个不同的列或者分区键不包含在主键中,在进行插入操作时,虽然也指定了分区键,但还是需要扫描所有分区才能判断插入的主键值是否违反了唯一性约束。这样的话,效率会比较低下,违背了分区表的初衷。
上面的分区类别中提到了各类分区的使用场景,其实分区怎么用,还是要看业务逻辑。
下面有一些分区使用的建议:
索引分区是指在OceanBase数据库中,根据一定的规则将索引数据拆分成多个部分,每个部分称为一个分区。这些分区可以独立存储、查询和管理,从而提高了数据库的整体性能。
OceanBase支持多种类型的索引分区,以适应不同的应用场景和需求。主要包括:
此外,根据索引键值是否具有唯一性,索引还可以分为唯一索引和非唯一索引。同时,如果分区索引表的分区键是索引列的左前缀,那么该索引被称为前缀索引;反之,则称为非前缀索引。
下面以一个简单的示例来介绍OceanBase分布式索引的使用方法。
创建分布式表和索引
CREATE TABLE t_user (
id INT,
name VARCHAR(50),
age INT,
PRIMARY KEY(id)
) DISTRIBUTE BY HASH(id);
CREATE INDEX idx_name ON t_user(name) LOCAL;
上述代码创建了一个名为t_user的分布式表,其中id字段作为主键,并采用哈希算法进行数据分布。同时,创建了一个名为idx_name的分布式索引,它只在本地节点上存储索引数据。