【最佳实践】分区策略和管理分区计划的实践方案

所有刚开始使用 OceanBase 社区版的用户,几乎都会问到 “如何设计分区表,以及如何管理表分区” 这个问题。

这两天在社区博客里,发现有用户写了一篇通过在 ODC 中制定分区策略和管理分区计划的实践方案,介绍了如何利用 ODC 来管理表分区,在这里和大家分享:《zabbix 大表在OB中实现表分区管理》

最后附上两篇官网上 ODC 的相关文档:

6 个赞

如果大家有任何和分区管理相关的疑问,都欢迎在本贴中进行留言评论,我们会第一时间对大家的问题进行回复。

3 个赞

现在我们有一张订单流水表,数据将近1个亿,按时间周期汇总数据的时候很慢,想考虑按年份对数据进行分区。现在只有ID是主键。尝试了一下好像无法按日期进行分区。是必须要把日期做成和ID的联合主键才可以分区麽? 这样做主键会对查询和插入性能产生影响麽?

按日期分区是否能达到硬件配置不变,查询变快的目的?

3 个赞

第一个问题:现在只有ID是主键。尝试了一下好像无法按日期进行分区。是必须要把日期做成和ID的联合主键才可以分区麽?

回答:是的,因为主键的唯一性检查是在各个分区内部进行的,如果主键不包含全部分区键,这个检查就会失效,所以 MySQL 也一样有这个要求。

-- 如果主键不包含全部分区键,建表就会失败报错,报错信息也挺明确的。
create table t1(c1 int, 
                c2 int,
                c3 int,
                primary key (c1))
partition by range (c2) 
  (partition p1 values less than(3),
   partition p1 values less than(6));
  
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

下面举个例子:

create table t1(c1 int, 
                c2 int,
                c3 int,
                primary key (c1, c2))
partition by range (c2) 
  (partition p0 values less than(3),
   partition p1 values less than(6));
Query OK, 0 rows affected (0.146 sec)

obclient [test]> insert into t1 values(1, 2, 3);
Query OK, 1 row affected (0.032 sec)

obclient [test]> insert into t1 values(1, 5, 3);
Query OK, 1 row affected (0.032 sec)

obclient [test]> select * from t1;
+----+----+------+
| c1 | c2 | c3   |
+----+----+------+
|  1 |  2 |    3 |
|  1 |  5 |    3 |
+----+----+------+
2 rows in set (0.032 sec)

我们创建了一张表,主键是 c1 和 c2,分区键是 c2,小于 3 的值在 p0 分区,大于等于 3 且小于 6 的值在 p1 分区。然后插入了两个行,第一行在 p0 分区,第二行在 p1 分区。

obclient [test]> select * from t1 PARTITION(p0);
+----+----+------+
| c1 | c2 | c3   |
+----+----+------+
|  1 |  2 |    3 |
+----+----+------+
1 row in set (0.033 sec)

obclient [test]> select * from t1 PARTITION(p1);
+----+----+------+
| c1 | c2 | c3   |
+----+----+------+
|  1 |  5 |    3 |
+----+----+------+
1 row in set (0.034 sec)

如果主键只有 c1 而没有 c2,那么在 p0 和 p1 分区内对 c1 列的唯一性检测都会成功,因为在各个分区内 c1 列的值都不重复,然后就会判定插入的数据符合主键约束。

但是实际上 c1 在分区间会有重复值,数据并不符合主键约束,所以所有数据库在分区时,都要求主键包含全部分区键。

第二个问题:这样做主键会对查询和插入性能产生影响麽?

回答:这样做对主键性能没啥影响。但是要注意主键的语义会发生变化,例如在不同分区间,允许 id 列出现相同的值了,需要评估下这个语义变化是否符合预期。

第三个问题:按日期分区是否能达到硬件配置不变,查询变快的目的?

回答:分区可以达到加速查询的目的,因为查询时会利用过滤条件里面的分区键进行分区裁剪。

例如下面这两个例子:

  • 如果过滤条件里有分区键,计划中可以看到 partitions(p0),说明只扫描了 p0 这一个分区的数据。
obclient [test]> explain select * from t1 where c2 = 1;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| ===============================================                                    |
| |ID|OPERATOR       |NAME|EST.ROWS|EST.TIME(us)|                                    |
| -----------------------------------------------                                    |
| |0 |TABLE FULL SCAN|t1  |1       |3           |                                    |
| ===============================================                                    |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([t1.c1], [t1.c2], [t1.c3]), filter([t1.c2 = 1]), rowset=16            |
|       access([t1.c1], [t1.c2], [t1.c3]), partitions(p0)                            |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],  |
|       range_key([t1.c1], [t1.c2]), range(MIN,MIN ; MAX,MAX)always true             |
+------------------------------------------------------------------------------------+
11 rows in set (0.034 sec)

如果过滤条件里没有分区键,计划中可以看到 partitions(p[0-1]),说明扫描了 p0 和 p1 全部所有分区的数据。其中 PX PARTITION ITERATOR 算子就是用来循环扫描所有分区的迭代器。

obclient [test]> explain select * from t1 where c3 = 1;
+------------------------------------------------------------------------------------+
| Query Plan                                                                         |
+------------------------------------------------------------------------------------+
| =============================================================                      |
| |ID|OPERATOR                 |NAME    |EST.ROWS|EST.TIME(us)|                      |
| -------------------------------------------------------------                      |
| |0 |PX COORDINATOR           |        |1       |6           |                      |
| |1 |└─EXCHANGE OUT DISTR     |:EX10000|1       |6           |                      |
| |2 |  └─PX PARTITION ITERATOR|        |1       |5           |                      |
| |3 |    └─TABLE FULL SCAN    |t1      |1       |5           |                      |
| =============================================================                      |
| Outputs & filters:                                                                 |
| -------------------------------------                                              |
|   0 - output([INTERNAL_FUNCTION(t1.c1, t1.c2, t1.c3)]), filter(nil), rowset=16     |
|   1 - output([INTERNAL_FUNCTION(t1.c1, t1.c2, t1.c3)]), filter(nil), rowset=16     |
|       dop=1                                                                        |
|   2 - output([t1.c1], [t1.c2], [t1.c3]), filter(nil), rowset=16                    |
|       force partition granule                                                      |
|   3 - output([t1.c1], [t1.c2], [t1.c3]), filter([t1.c3 = 1]), rowset=16            |
|       access([t1.c1], [t1.c2], [t1.c3]), partitions(p[0-1])                        |
|       is_index_back=false, is_global_index=false, filter_before_indexback[false],  |
|       range_key([t1.c1], [t1.c2]), range(MIN,MIN ; MAX,MAX)always true             |
+------------------------------------------------------------------------------------+
19 rows in set (0.038 sec)
5 个赞

除此以外,就像楼顶帖子里推荐的那篇博客所介绍的一样,OB 还提供了相应的工具 ODC(OceanBase 开发者中心),可以直接通过这个工具设置分区管理策略,自动完成分区的创建和删除等操作,无需手动维护脚本。

例如可以通过 ODC 保留最近的 90 个分区,即定期删除最新 90 个分区以外的其它分区。ODC 的使用方式详见 OceanBase 官方文档:https://www.oceanbase.com/docs/common-odc-1000000001146951。

5 个赞

膜拜,非常专业易懂。在实操过程中遇到了问题,有时间的话帮忙出个主意看看咋整
非分区表转换成分区表:range出错 - 社区问答- OceanBase社区-分布式数据库

3 个赞

简单看了下你这个帖子里的问题,range 分区支持的数据类型不包括 datetime,详见:官方文档


ob 的行为和 MySQL 是兼容的,试了下 MySQL,也会报一样的错。
尝试用生成列,ob 和 MySQL 也都不支持在生成列里使用这个特殊的表达式 UNIX_TIMESTAMP,报错也是一样的。发现 ob 的 MySQL 兼容性做的还不错,哈哈~

不过,我理解是不是改成类似于下面这样的 range columns 分区就行了?

CREATE TABLE ff01 (a datetime , b timestamp)
PARTITION BY RANGE columns(a)(
                    PARTITION p0 VALUES less than ('2023-01-01'),
                    PARTITION p1 VALUES less than ('2023-01-02'),
                    PARTITION pn VALUES less than MAXVALUE);

虽然 range columns 分区不能使用表达式,但是 range columns 不要求分区键是整型,您这个 datetime 类型也是可以支持的。如果业务不强制要求必须用 range 分区,可以考虑下使用 range columns 分区,和 range 分区的语义几乎一样。详见:MySQL 官网文档

5 个赞

根据您的几个问题简单总结了一篇博客,详见《浅析创建分区时常见的几个问题》。您发的那个帖子里的报错,直接原因应该是分区函数用了 nondeterministic function,详见这篇博客吧,哈哈~

6 个赞