分區表是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
關注公衆號,查看更多歷史文章分享。