分区表是ORACLE从8.0开始引入的功能,也是第一个支持物理分区的数据库,随后其余数据库纷纷跟进。分区表是一种“分而治之”的思想,经过将大表、索引分红能够独立管理的、小的片断(Segment
),为海量数据访问提供了可伸缩的性能。自从 Oracle 引入分区技术以来,Oracle 公司在每次推出重要版本时都会对分区方法或功能上有所加强。在ORACLE 12c以前,ORACLE分区表始终在一个实例范围内,没法突破实例的存储空间瓶颈,12c以后ORACLE SHARDING 支持分区表的不一样分区能够分布在不一样的ORACLE实例里。这其实是ORACLE吸取了其余分布式数据库中间件的优势而作的一个改进。数据库
OceanBase是阿里巴巴和蚂蚁金服彻底自主研发的通用的分布式关系型数据库,从1.0版本开始OceanBase就支持分区表,功能逐步跟ORACLE分区表兼容,而且支持不一样分区分布在集群的不一样节点(机器)上。本文是对OceanBase分区表的能力作一个详细介绍。负载均衡
虽然“分区”的概念不是很新,可是“分区”对理解OceanBase的不少原理倒是很是重要的。less
从水平拆分设计上说,目前分布式数据库产品里有三种拆分途径。一是以Google、TiDB为表明的在存储层按定长块切片的,称为Region
,拆分细节对业务彻底透明。二是以ORACLE、OceanBase为表明的使用分区表的多分区拆分,业务须要指定拆分策略和分片数,使用上基本上跟单表同样。三是以DRDS、TDSQL等为表明的分布式数据库中间件的分库分表拆分,业务使用的是一个逻辑表,实际数据存放在多个结构相同命名或位置不一样的物理表上。分布式
OceanBase里一个非分区表只有一个分区,一个分区表有多个分区。分区就是表的子集。OceanBase里单个分区只能在一个节点上,不一样分区能够在不一样节点上。函数
分区的好处有:性能
提升可扩展性。分区表的不一样分区能够分布在不一样的机器上,使得单表能得到多机的处理能力,而且使得单表的容量能够超过单机的容量。性能也是同理。优化
提升可管理性。对于数据操做的粒度能够控制在单个分区。例如按照时间分区的数据,能够经过drop一个分区来实现数据过时功能。spa
提升性能。经过分区裁剪,能够快速定位到用户须要查询的分区。提升查询性能。设计
在OceanBase里,每一个数据有三份,每一个具体的分区也有三份,分布在不一样的Zone里的不一样节点上。每一个分区有三份副本,副本内容相同,角色上有区分,是1个leader
副本和2个follower
副本。有时候会简单说1个主副本2个备副本。可是主备的概念容易引发误解。code
默认业务只有leader
副本提供读写服务,follower
副本只同步数据,不提供服务。特殊场景下,业务SQL使用弱一致性读Hint(即read_consistency(weak)
)能够就近读取follower
副本。此外OBProxy
的LDC
设置也能够作到就近读取某个合适的follower
副本(这个之后再细说)。数据的变动在leader
副本,事务提交的时候,leader
副本会就Redo
落盘发起表决,使用Paxos
协议。具体就是除了本身把Redo
落盘,同时还发往两个follower
副本,follower
副本收到redo
落盘后表决“成功”。同时Follower
副本开始应用该Redo
。三副本里只要有一半以上成员(2个副本)表决落盘成功,leader
副本上的业务的事务就提交成功返回消息给客户端。
每一个分区的三副本组成一个独立的Paxos
小组,相应的Redo
在副本之间传输。因此说分区是数据同步的最小单元。而且这种Redo
同步是自动的,不须要也不能干预的。
每一个分区的三副本会保持数据同步,目地是为了保证在Leader
副本不可用的时候选举出新的Leader
副本拥有所有的数据。Paxos
协议保证了Redo
会在至少一个Follower
副本里有(最终会全部Follower
副本都有)。三副本会跟OceanBase集群的rootservice
服务维持心跳,当Leader
副本不可用时,通过2个租约时间后rootservice
会选举出新的Leader
出来,在应用完Redo
后新Leader
提供读写服务。
分区的选举是自动的,只要多数派存活,就不须要人工介入。因此说“分区”是高可用的最小单元。OceanBase的“切换”指的就是一个个Leader
分区从新选举的过程,并非实例级别的“切换”。当一个机器节点挂掉后, 严格的说,其影响只是局部的数据(Leader
副本)的读写访问短暂中断)。在OceanBase里,通常不会说某台机器是主,某台机器是备,由于理论上全部的机器均可能存在Leader
副本,都能提供读写服务。
数据表中每一行中用于计算这一行属于哪个分区的列的集合叫作分区键。由分区键构成的用于计算这一行属于哪个分区的表达式叫作分区表达式。
因为OceanBase的表是索引组织表(IOT
),为了保证主键的含义:给定主键的查询能很快定位到所在的分区。因此分区键必须是主键的子集。若是这个表里面还含有惟一索引,那么分区键就必须是全部惟一索引列(包括主键列)交集的子集。
Oracle的IOT
表和索引分区也有这个要求,堆表没这个要求。
分区表的索引分局部索引和全局索引。
局部索引是局限在单个分区内的索引。对于非分区的普通表,它的索引存储是整个表的行按照索引键进行排序后的结果。对于分区表的局部索引是每一个分区分别本身存储本身的行按照索引键进行排序后的结果,不是全局有序的。建立语句中须要带local
关键字,举个例子:
CREATE TABLE `sbtest` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`, k) ) PARTITION BY HASH(k) PARTITIONS 8; CREATE INDEX k_ind1 ON `sbtest`(`k`) LOCAL;
OceanBase 1.x版本因为不支持“全局一致性快照”功能,因此只支持局部索引,不支持全局索引。OceanBase 2.x版本支持全局索引。去掉上面的LOCAL
关键字就是全局索引。
根据数据分区的策略,分为三大类:
hash
分区/key
分区。
range
分区/range columns
分区。
list
分区/list columns
分区。
按照分区的维度,能够分为:
一级分区。
二级分区。
二级分区是一级分区的二次拆分。因此一级分区有一个拆分键,二级分区有两个拆分键,而且两次拆分的策略能够不同。
二级分区支持下面几种分区策略组合:
hash
+ hash
hash
+ range
hash
+ key
key
+ key
range
+ range
hash
分区Hash分区须要指定分区键和分区个数。经过hash的分区表达式计算获得一个整数,这个结果再跟分区个数取模获得具体这行数据属于那个分区。一般用于给定分区键的点查询,例如按照用户id来分区。hash分区一般能消除热点查询。
示例:
CREATE TABLE `sbtest` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`, k) ) PARTITION BY HASH(k) PARTITIONS 8;
hash分区表达式不支持向量,即不支持 hash by (c1,c2)
。
key
分区key分区跟hash分区相似,也是经过对分区个数取模的方式来肯定数据属于哪一个分区。不一样的是系统会对key分区键作一个内部默认默认的hash函数后再取模。因此用户一般没有办法本身经过简单的计算来得知某一行属于哪一个分区。
示例:
CREATE TABLE `sbtest_x1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`, c) ) PARTITION BY KEY(c) PARTITIONS 8;
key
分区跟hash
分区的区别:
hash
分区表达式结果必须是整数,key
分区没有这个要求,支持字符列。
hash
分区的分区键支持表达式,key
分区不支持。
range
分区和range columns
分区range分区是按照分区表达式的范围来划分分区。一般用于对分区键须要按照范围的查询。例如经过按照时间字段进行范围分区,还有价格区间等一些分区方式。
示例:
create table t1 (c1 int, c2 int) partition by range(c1) ( partition p0 values less than(100), partition p1 values less than(500), partitions p2 values less than(maxvalue) );
range分区的限制和要求:
分区表达式的结果必须是整数类型。
不能写向量,例如partition by range(c1, c2)
。
range columns和range的区别是:
range columns分区不要求是整数,能够是任意类型。
range columns分区不能写表达式,可是能够是向量。
目前提供对range
分区的分区操做功能,能add
/drop
分区。add
分区如今只能加在最后,因此最后不能是maxvalue的分区。若是是maxvalue的分区要增长一个分区,只能作分区分裂(split
)。分区分裂还在研发中。
list
分区和list columns
分区list分区是根据枚举类型的值来划分分区的。主要用于枚举类型。
示例:
create table t1 (c1 int, c2 int) partition by list(c1) ( partition p0 values in (1,2,3), partition p1 values in (5, 6), partition p2 values in (default) );
list
分区的限制和要求:
分区表达式的结果必须是整数。
不能写向量,例如partition by list(c1, c2)
。
list columns和list的区别是:
list columns
分区不要求是整数,能够是任意类型。
list columns
分区不能写表达式,但能够是向量。
按照两个维度来把数据拆分红分区。
最经常使用的地方就是相似用户帐单领域,会按照user_id作hash分区,按照帐单建立时间作range分区
hash/key + range/range columns分区
range/range columns + hash/key分区
示例:
CREATE TABLE history_t( user_id INT, gmt_create DATETIME, info VARCHAR(20), PRIMARY KEY(user_id, gmt_create)) PARTITION BY HASH(user_id) SUBPARTITION BY RANGE COLUMNS(gmt_create) SUBPARTITION TEMPLATE ( SUBPARTITION p0 VALUES LESS THAN ('2014-11-11'), SUBPARTITION p1 VALUES LESS THAN ('2015-11-11'), SUBPARTITION p2 VALUES LESS THAN ('2016-11-11'), SUBPARTITION p3 VALUES LESS THAN (MAXVALUE) ) PARTITIONS 3;
Oracle的二级分区支持每一个一级分区的二级分区分区定义不同。支持相似下面这种
create table t1 (c1 int, c2 int) partition by range(c1) subpartition by hash(c2) ( partition p0 values less than(100) (subpartition sp0), partition p1 values less than(200) (subpartition sp2, subpartition sp3) ) ;
一级分区p0下面有一个分区,而一级分区p1下面有两个分区。
OceanBase暂时不支持这样的分区方式,必须用template的分区方式。这致使对于range
分区的分区操做add
/drop
,必须是range
分区作为一级分区的方式。因此强烈建议用range + hash
的分区方式,而不是hash + range
.
当用户访问分区表时,每每只须要访问其中部分的分区。优化器根据SQL中所带的条件,避免访问无关分区的优化过程咱们称之为“分区裁剪”(Partition Pruning)。分区裁剪是分区表提供的重要优化手段,经过分区的裁剪,SQL的执行效率能够获得大幅度的提高。用户能够利用分区裁剪的特性,在访问中加入定位分区的条件,避免访问无关数据,优化查询性能。
分区裁剪自己是一个比较复杂的过程。优化器须要根据用户表的分区信息和SQL中给定的条件,抽取出相关的分区信息,因为SQL中的条件每每比较复杂,整个抽取逻辑的复杂性也随之增长,这一过程由OceanBase中的Query Range子模块完成。
示例:
MySQL [sysbenchtest]> explain select * from sbtest where k='264454'\G *************************** 1. row *************************** Query Plan: ============================================= |ID|OPERATOR |NAME |EST. ROWS|COST| --------------------------------------------- |0 |TABLE SCAN|sbtest(k_ind1)|3 |99 | ============================================= Outputs & filters: ------------------------------------- 0 - output([sbtest.id], [sbtest.k], [sbtest.c], [sbtest.pad]), filter(nil), access([sbtest.k], [sbtest.id], [sbtest.c], [sbtest.pad]), partitions(p6) 1 row in set (0.01 sec)
OceanBase官方文档 https://oceanbase.alipay.com/docs
关注公众号,查看更多历史文章分享。