OB君:本文是 「 OceanBase 2.0 技術解析系列」 的第九篇文章。今天咱們來聊聊2.0的全局索引功能。本文將帶你簡單回顧全局索引的概念,並詳細介紹OceanBase 2.0版本如何實現全局索引的功能。更多精彩關注OceanBase公衆號持續訂閱本系列內容!
在數據庫領域中,「分區表」的概念你們並不陌生,可是分區表中「本地索引(Local Index)」和「全局索引(Global Index)」的概念,未必每一個朋友都關注過。和本地索引相比,全局索引在使用上更加靈活方便,在不少場景下也能提供更好的查詢性能,對開發人員來講更加友好。可是全局索引的實現也會有更大的難度,尤爲是在分佈式環境下,實現難度更大。數據庫
本文將幫助讀者簡單回顧一下全局索引相關的概念,並介紹OceanBase數據庫如何在2.0版本中實現全局索引的功能。網絡
爲了方便你們閱讀,咱們先約定一些固定的術語,後文的描述中會統一採用這些術語:數據結構
指使用CREATE TABLE語句建立的表對象。也是索引對象所依賴的表(即CREATE INDEX語句中ON子句所指定的表)。架構
指使用CREATE INDEX語句建立的索引對象。有時爲了便於你們理解,也會把索引對象類比爲一個表對象,即索引表。併發
此外,爲了方便你們理解文字描述,咱們會以一個實際的數據庫表來演示各類狀況,這個表的名字叫employee(員工信息表),表的結構以下:負載均衡
employee
{
emp_id, /* 員工ID */
emp_name /* 員工名字*/
dpet_id, /* 部門ID */
...
}
首先,咱們來簡單回顧一下傳統的「非」分區表中,主表和索引的對應關係。主表的全部數據都保存在一個完整的數據結構中,主表上的每個索引也對應一個完整的數據結構(好比最多見的B+ Tree),主表的數據結構和索引的數據結構之間是一對一的關係,以下圖所示:運維
上圖展現了employee表中,以emp_id建立的索引。分佈式
當分區表出現以後,狀況發生了變化:主表的數據按照分區鍵(Partitioning Key)的值被分紅了多個分區,每一個分區都是獨立的數據結構,分區之間的數據沒有交集。這樣一來,索引所依賴的單一數據結構不復存在,那索引須要如何應對呢?這就引入了「本地索引」和「全局索引」兩個概念:性能
1)本地索引(Local Index)優化
分區表的本地索引和非分區表的索引相似,索引的數據結構仍是和主表的數據結構保持一對一的關係,但因爲主表已經作了分區,主表的「每個分區」都會有本身單獨的索引數據結構。對每個索引數據結構來講,裏面的鍵(Key)只映射到本身分區中的主表數據,不會映射到其它分區中的主表,所以這種索引被稱爲本地索引。
從另外一個角度來看,這種模式下索引的數據結構也作了分區處理,所以有時也被稱爲本地分區索引(Local Partitioned Index)。本地索引的結構以下圖所示:
在上圖中,employee表按照emp_id作了範圍分區,同時也在emp_name上建立了本地索引。
2)全局索引(Global Index)
和分區表的本地索引相比,分區表的全局索引再也不和主表的分區保持一對一的關係,而是將全部主表分區的數據合成一個總體來看,索引中的一個鍵可能會映射到多個主表分區中的數據(當索引鍵有重複值時)。更進一步,全局索引能夠定義本身獨立的數據分佈模式,既能夠選擇非分區模式也能夠選擇分區模式;在分區模式中,分區的方式既能夠和主表相同也能夠和主表不一樣。所以,全局索引又分爲如下兩種形式:
索引數據不作分區,保持單一的數據結構,和非分區表的索引相似。但因爲主表已經作了分區,所以會出現索引中的某一個鍵映射到不一樣主表分區的狀況,即「一對多」的對應關係。全局非分區索引的結構以下圖所示:
在上圖中,雖然employee表按照emp_id作了分區,可是建立在dept_id上的全局索引並無分區,所以同一個鍵值可能會映射到多個分區中。
索引數據按照指定的方式作分區處理,好比作哈希(Hash)分區或者範圍(Range)分區,將索引數據分散到不一樣的分區中。但索引的分區模式是徹底獨立的,和主表的分區沒有任何關係,所以對於每一個索引分區來講,裏面的某一個鍵均可能映射到不一樣的主表分區(當索引鍵有重複值時),索引分區和主表分區之間是「多對多」的對應關係。全局分區索引的結構以下圖所示:
在上圖中,employee表按照emp_id作了範圍分區,同時在emp_name上作了全局分區索引。能夠看到同一個索引分區裏的鍵,會指向不一樣的主表分區。
因爲全局索引的分區模式和主表的分區模式徹底沒有關係,看上去全局索引更像是另外一張獨立的表,所以也會將全局索引叫作索引表,理解起來會更容易一些(和主表相對應)。
這裏特別說一點:「非」分區表也能夠建立全局分區索引。但若是主表沒有分區的必要,一般來講索引也就沒有必要分區了。後文中會忽略這種特殊狀況,主要討論分區表中的本地索引和全局索引。
那麼,對於分區表來講,本地索引和全局索引哪種更好呢?其實兩種索引是各有優劣勢的,下面分別加以說明。
首先來講本地索引。它最明顯的好處就是實現簡單,但本地索引的問題也很明顯,尤爲是「索引鍵沒有包含主表全部的分區鍵字段」的狀況,此時對某一個索引鍵值來講,對應的索引數據在全部分區的本地索引中均可能存在,這會引起兩個突出的問題:
1. 因爲本地索引只處理一個主表分區的數據,所以只能在一個主表分區內保證索引鍵的「惟一性約束」,沒法在全表範圍內保證索引鍵的惟一性約束,以下圖所示:
上面的圖中,employee按照emp_id作了範圍分區,但同時想利用本地索引創建關於emp_name的惟一約束,這是根本沒法實現的。
針對這個問題,有些數據庫在分區表中會增長限制,要求主鍵和惟一索引的定義中必須包含主表全部的分區鍵字段。有了這個限制,索引中的某一個鍵所對應的索引數據只可能存在於一個分區中,所以只要在每個分區內保證惟一性約束,便可在全表範圍內保證惟一性約束。這個限制雖然解決了數據庫的難題,卻大大增長了開發人員的煩惱,由於它會致使主鍵和惟一索引的可選範圍大大縮小(只能是主表分區鍵的「超集」),不少業務需求所以沒法知足,這也是本地索引最被詬病的地方。
2. 因爲某一個索引鍵值在全部分區的本地索引上均可能存在,任何索引掃描必須在全部的分區上都作一遍,以避免形成數據遺漏。
這會致使索引掃描效率低下,而且會在全局範圍內形成CPU和IO資源的浪費。採用「分區間並行」的手段能夠提升效率,但不能從根本上解決這個問題。
下面來看看全局索引。相比本地索引來講,全局索引具備諸多優點,尤爲是針對前面談到的,當「索引鍵沒有包含主表全部的分區鍵字段」時本地索引所面臨的兩個突出問題:
1. 不能實現全局惟一性約束的問題。
對於全局索引來講,能夠爲索引數據指定本身的分區方式,而且索引的分區鍵必定是索引鍵的子集,所以能夠很容易解決這個問題。下面分別以幾種狀況作說明:
此時索引的結構和「非分區」表沒有區別,只有一個完整的索引樹,天然很容易保證惟一性。
這種狀況下,對於某一個索引鍵來講,因爲包含了全部的索引分區鍵,它的數據只可能落在一個固定的索引分區中,所以只要在每個索引分區內保證惟一性約束,就能夠在全表範圍內保證惟一性約束。而單個索引分區內只有一個索引數據結構,很容易保證惟一性約束,所以問題就獲得瞭解決。
2. 必須掃描「全部」分區所帶來的性能和資源浪費問題。
這個問題也能夠分幾種狀況分別說明:
前面講述惟一性約束的問題時提到了,此時只有一個完整的索引樹,天然沒有多分區掃描的問題。
前面講述惟一性約束的問題時提到了,全局索引能保證某一個索引鍵的數據只落在一個固定的索引分區中。對於索引鍵值的範圍掃描,咱們但願索引掃描按單向順序在每一個分區內都只執行一次,而沒必要在索引分區之間來回穿梭,所以咱們要求索引的分區鍵是索引鍵的「前綴(Prefix)」:好比索引鍵是"a,b",那麼索引的分區鍵能夠是"a"或者"a,b",但不能是"b",即要求分區的排序規則和索引樹的排序規則一致。這樣一來,不管是針對固定鍵值的索引掃描,仍是針對一個鍵值範圍的索引掃描,均可以直接定位出須要掃描的一個或者幾個分區,而不是像本地索引同樣盲目地在全部分區上掃描,這樣就解決了本地索引面臨的問題。
解決了上述問題以後,從使用者的角度來看,分區表的全局索引和非分區表的索引已經沒有太大區別,除了一點:全局索引須要定義索引的分區模式。這裏雖然也可使用全局「非分區」索引,可是引進全局索引的目的就是針對數據量較大的分區表,對應的索引數據量每每也是很是大的,所以仍是推薦使用全局分區索引,不但能夠突破非分區索引面臨的單點容量限制,在併發較大的狀況下也能得到更好的性能。
固然,全局索引也面臨一些問題,主要是架構複雜,實現難度大,以及由此引起的一些相關問題,好比當索引數據和對應的主表數據位於不一樣的機器時,在事務內會面臨數據一致性和性能方面的挑戰。但考慮到全局索引給數據庫使用者帶來的巨大便利,付出一點代價也是值得的。
綜合兩者的特色來看,本地索引和全局索引的適用場景是不同的:
能夠說,全局索引具備更強的通用性。
關於全局索引的實現原理,以及全局索引和本地索引的比較,前面都已經有過詳細的描述,這是業界實現全局索引的基本思路,也是OceanBase中實現全局索引的基本思路。可是,前面咱們更多的是討論索引數據結構和主表數據結構的映射關係(一對1、一對多、多對多等),而忽略了另一個很重要的因素要,那就是數據的物理分佈。
對於傳統的單點數據庫來講,每一個數據結構之下都是「本地可訪問」的存儲層,好比一個本地數據庫表空間(裏面包含若干本地文件或者設備),不管有多少個主表分區或者索引分區,數據庫老是能夠經過本地機器訪問到,即Share-Everything的架構。
可是對於OceanBase這樣的分佈式數據庫來講,每個主表分區和索引分區均可能分佈在不一樣的機器上,好比「N個主表分區+M個全局索引分區」這種多對多的複雜狀況,多是(N+M)臺物理機器上造成的(N*M)種索引鍵到主表分區的映射關係,這會帶來諸多挑戰:
能夠說,若是不能解決上述問題,全局索引在分佈式數據庫中就失去了存在的基礎。所以,雖然業內有不少分佈式數據庫,但全局索引功能卻並非一個標配,不少你們所熟悉的分佈式數據庫產品(如MongoDB,Cassandra,Hbase等)並不提供全局索引功能,只有少數產品才能提供,好比Google F1/Spanner和CouchBase,這也從一個側面印證了在分佈式數據庫中實現全局索引的難度。下面我就簡單給你們介紹一下,OceanBase數據庫是如何跨越上述的一個個難關,在2.0版本中實現了全局索引的功能。
首先來看第一個問題,如何保證主表數據和索引數據的跨機器同步更新。這個問題的本質,實際上是分佈式數據庫中「分佈式事務一致性」問題,主表數據和索引數據是同一個事務中的兩部分數據,當它們分佈在不一樣的機器上時,分佈式事務須要保證事務的原子性(Atomicity):兩部分數據要麼所有更新成功,要麼所有失敗,不會因事務異常而致使主表數據和索引數據的不一樣步。
OceanBase數據庫在幾年前的1.0版本中就提供了分佈式事務功能,利用Paxos協議和通過改良的「兩階段提交(Two-phase Commit)」方法,能在跨機器的分佈式事務內保證ACID,即便事務的參與者發生異常(如機器宕機),也能確保分佈式事務的完整性,避免了傳統兩階段提交方法會致使的「事務部分未決(In-doubt Transaction)」問題,所以OceanBase徹底能夠保證跨機器事務中主表數據和索引數據的同步。關於OceanBase數據庫分佈式事務的詳細內容,讀者能夠查閱本文末尾參考文檔中的相關文章。
解決了分佈式事務的一致性問題,咱們來關注一下第二個問題,就是索引數據和主表數據分跨機器訪問時的效率問題。這裏面臨的最大挑戰就是分佈式事務的網絡延遲和屢次日誌落盤,而這種物理開銷是沒有辦法徹底消除的,爲此Google在F1的論文中明確說明不推薦太多的全局索引,而且應儘可能避免對有全局索引的表作大事務訪問。那OceanBase是如何應對這個問題的呢?
前面提到過,OceanBase使用了通過改良的兩階段提交方法,這種改良不只體如今保證數據的一致性上,也體如今性能上:和傳統的兩階段提交相比,OceanBase的兩階段提交過程當中會有更少的網絡交互次數以及更少的寫日誌次數,而這些偏偏都是分佈式事務中最耗時的操做。有了這樣的優化,在很大程度上縮短了分佈式事務的處理時間,提升了全局索引的處理效率。
最後一個問題,則是分佈式數據庫中全局(跨多臺機器)的讀寫一致性問題。對OceanBase這樣實現了快照隔離級別(Snapshot Isolation)和多版本併發控制(MVCC)的分佈式數據庫來講,如何在機器間有時鐘差別的狀況下,仍能維持時間戳(即版本號)在全局範圍內的先後一致性,這是一個很重要的問題。
在OceanBase數據庫的2.0版本中,咱們提供了「全局一致性快照」功能,並在此基礎上實現了全局範圍內的快照隔離級別和多版本併發控制,這樣就能保證全局範圍內先後事務的讀寫一致性,知足了全局索引的要求。關於OceanBase數據庫全局一致性快照的詳細內容,讀者能夠查閱本文末尾參考文檔中的相關文章。
所以,OceanBase數據庫的2.0版本解決了上述全部的技術難題,咱們也正式推出了全局索引功能。對於數據庫用戶來講,能夠再也不擔憂本地索引在分區表使用中的諸多不便,利用全局索引實現任意模式的全局一致性約束,而且爲更多的複雜查詢場景提供更優的性能,盡情體驗「全局索引+分佈式架構」帶來的完美體驗!
參考文檔
— 想了解更多OceanBase 2.0新特性?
— 想與螞蟻金服OceanBase的一線技術專家深刻交流?
掃描下方二維碼聯繫小編,快速加入OceanBase技術交流羣!