關於作者
謝振江,OceanBase 高階技術專家。2015年加入 OceanBase, 從事儲存引擎相關工作,目前在儲存-索引與 DDL 組,負責索引,DDL 和 IO 資源排程相關工作。
回顧關係型資料庫大規模應用以來的發展,從單機到分散式無疑是一個關鍵轉變,促成它的是層出不窮的新業務和爆發增長的資料量。
一方面,更大的資料量意味著社會經濟發展有了更多新可能,另一方面,它也對資料庫提出了更高要求,以避免儲存節點增多帶來的運維成本上升和可能出現的新故障。因此,做好分散式資料庫的透明性,像使用單機資料庫一樣使用分散式資料庫成為提升使用者體驗的關鍵之一。而 DDL 作為資料庫運維中常見的變更操作,它的執行自然應該對業務方和運維人員都透明。
“DDL 操作一定要等到夜深人靜時”、“DDL操作執行時間很長,有時甚至需要幾周時間”……這些問題不僅令一線運維人員抓狂,也是各個資料庫廠商一直嘗試突破的地方。OceanBase 認為,上述問題的完美解決取決於實現高效且透明的 DDL,即資料庫應該儘可能快地執行 DDL,並保證 DDL 執行期間不影響業務方和運維人員的操作。
在 OceanBase 4.0 中,我們基於已有的原生 Online DDL 能力進行了創新:首先,為了提升 DDL 操作的可用性,我們自研了一套專門用於旁路寫入的資料同步方法;此外,我們對 DDL 的單機效能和分散式執行的可擴充套件性進行最佳化,大幅提升使用者 DDL 操作的響應速度;最後,4.0 進一步完善了原生 Online DDL 的框架能力,支援了主鍵變更、分割槽規則修改、修改列型別和修改字符集等功能。
我們希望透過這些新變化,幫助使用者輕鬆應對各類複雜業務場景。本篇文章將討論 OceanBase 如何實現高效且透明的 DDL,介紹 OceanBase 4.0 DDL 的新能力及設計思路:
- 更適合使用者的 DDL 是什麼樣的;
- OceanBase 如何做好 DDL;
- 來看看 4.0 DDL 有哪些新變化;
- 4.0 DDL 新功能上手實測。
更適合使用者的 DDL,應該是什麼樣的?
要回答這個問題,我們需要先了解 DDL 的概念。在實際的資料庫運維過程中,除了 SELECT
,INSERT
,UPDATE
,DELETE
等這些常用的對資料本身進行操作處理的命令,還有 CREATE
,ALTER
,DROP
,TRUNCATE
等對錶結構等資料庫物件進行變更、與資料定義相關的命令,這些命令被稱為 DDL。舉例來說,常見的 DDL 操作有在表上增加新列、或給某列新增索引等。
在資料庫發展早期,執行 DDL 語句被視為最昂貴的資料庫操作之一,這是因為 DDL 操作通常會造成表不可讀寫,阻塞各類進行中的任務。如果表的資料量很大,會長時間中斷資料庫服務來執行 DDL,這對於被要求時刻線上的關鍵業務是難以接受的。因此,Online DDL 應運而生,它的提出主要是為了能夠在執行 DDL 時,不阻塞正常的使用者請求。目前市面上大部分資料庫的 Online DDL 並未做到對使用者完全透明:
- 大部分單機資料庫在 Online DDL 過程中存在短暫加鎖的操作,例如使用 MySQL 資料庫在大事務場景中執行 DDL,可能會阻塞使用者的請求;
- 受限於架構設計,目前業界許多分散式資料庫實現的 Online DDL,在某些業務場景會對使用者正常的請求造成影響;
- 此外,Online DDL 產生於單機資料庫,它通常只關注 DDL 是否對正常使用者請求產生影響,而對資料庫節點發生異常,如當機時的應對並未提及。
而隨著資料量爆發時代的到來,DDL 的執行時長也會制約業務的迭代速度。在單機資料庫中,通常會採用並行排序來儘可能加速 DDL 的執行,但它的執行速度受限於單機的效能瓶頸;到了分散式資料庫,業界普遍使用模擬使用者插入的方式來補全資料,然而這種方式並不能充分利用單臺伺服器的效能,也忽視了分散式可擴充套件能力提供的潛在效能。
可以這樣說,僅靠傳統意義上的 Online DDL 已無法很好地滿足實際業務需求。
我們認為,更適合使用者實際業務需求的 DDL 至少要具備以下兩方面能力:一方面,DDL 的執行應避免對業務方的 DML、DQL 操作產生影響,分散式環境也不會被運維人員感知,即便發生當機等異常,DDL 操作也能成功;另一方面,DDL 操作應具備在單機和分散式上良好的並行處理能力,幫助使用者進行快速的業務創新。
OceanBase 如何做好 DDL
OceanBase 希望為使用者提供一款高效並且足夠透明的資料庫產品。
在透明性方面:不同於單機資料庫,我們需要解決 DDL 操作過程中分散式資料庫多節點狀態不一致的問題。在應對這個問題時,業界的大部分資料庫採用了“DDL 優先”設計思路,然而這種思路會在一些業務場景對使用者請求產生影響。而 OceanBase 採用的是“業務請求優先”設計思路,能避免對業務請求產生影響。此外,我們也儘量遮蔽使用者對分散式資料庫的感知,讓使用者像使用單機資料庫一樣,在分散式資料庫中執行 DDL。
在執行效率方面:考慮到部分 DDL 操作會對資料進行補全,而該步驟往往是 DDL 操作中最耗時的,我們沒有使用其他分散式資料庫常用的模擬插入方式,而是借鑑了單機資料庫的設計思路,並在分散式資料庫中做到資料補全的效能可擴充套件,實現足夠高效的 DDL。
▋ 業務請求優先的分散式 Online DDL
在介紹 OceanBase 的 Online DDL 前,不得不提的是分散式資料庫領域中較為流行的 Google F1 線上非同步 Schema 變更演算法(出自論文《Online, Asynchronous Schema Change in F1》),CockroachDB 等眾多分散式資料庫的 Online DDL 功能均基於這一演算法實現。該演算法的實際過程較為複雜,如果嘗試用簡單的話解釋可以這樣表述:由於執行 DDL 過程中不能對錶進行禁寫,大機率會出現不同節點有不同 schema 版本的情形,而該演算法會透過引入多箇中間狀態的 schema,以保證資料的一致性。
進一步說,Google F1 資料庫由於沒有全域性成員列表,所以在 DDL 執行過程中無法考慮機器和事務的狀態,會強制地週期性往前推進 Schema 版本,而叢集中需要保證同時使用的 Schema 版本不超過兩個,因此,對於事務的執行時長有限制,並且如果發生節點獲取不到最新 Schema 版本的情況,該節點會自殺退出,影響其上所有的事務執行。總結而言,F1 資料庫會優先推進 DDL 的執行程式,而不考慮對事務狀態的影響,因此,我們將 F1 資料庫的設計思路稱為 DDL 優先的設計。
與 Google F1 不同的是,OceanBase 的特點是有全域性的成員列表,在 DDL 變更過程中,可以與 DDL 變更表格相關的成員進行協調,當所有節點的事務狀態都滿足 DDL 變更一致性所需的條件時,才推進接下來的 Schema 版本,這樣可以避免限制正常事務的執行,在出現節點無法重新整理到最新 Schema 版本時,OceanBase 不會殺掉節點,只會限制該節點正在執行 DDL 語句的表格相關的事務的執行,同時不影響其他表格的執行,因此,我們將 OceanBase 的設計思路稱為業務請求優先的設計。
我們以建索引為例,測試 Google F1 和 OceanBase 在不同場景中執行 DDL 對業務請求的影響,結果如下:
OceanBase 3.x | Google F1 | |
---|---|---|
對正常事務的影響 | 無影響 | 限制執行時長 |
無法獲取最新 Schema 時 | 程式存活,影響正在執行DDL表格的相關的使用者事務 | 事務結束,程式自殺 |
節點當機時 | 執行時間變長 | 無影響 |
表1 OceanBase 3.x 與 Google F1 建索引影響對比
而在 OceanBase 4.0 中,Online DDL 除了具備業務請求優先,對業務透明的特點之外,還在資料庫節點出現異常場景時,增強了 DDL 操作的高可用特性,即使在節點當機時,執行時間也不一定會變長,具體內容會在後文中詳細描述。
▋ 高效的資料補全方式
資料庫中有部分 DDL 操作需要對資料進行補全,比如建索引、加列等,OceanBase 從 1.4 版本開始將這些需要補全資料的 DDL 操作劃分為兩種模式:一種只需要在 Schema 上修改,非同步進行補資料的方式,或稱為 Instant DDL;另一種則是需要實時補全資料的 DDL。
對於需要實時補全資料的 DDL,業內大多數分散式資料庫採用的是模擬使用者插入的方式將資料補全,這種方案的優點是實現簡單,可以複用 DML 的寫入能力來完成資料寫入,並同步到備副本、備庫等,其缺點是效能差,資料寫入時會經過 SQL、事務、記憶體排序的結構,在 LSM-Tree 儲存架構中資料最終還要經過多次 compaction,步驟繁瑣。因此,OceanBase 借鑑了單機資料庫排序和旁路寫入的思路進行資料補全。但與單機資料庫不同的是,OceanBase 需要進行分散式排序,以及結合 LSM-Tree 儲存架構展開最佳化以獲得更好的效能。
一、分散式排序
在 OceanBase 3.x 版本中,DDL 中的分散式排序複用了舊的 SQL 執行框架中的分散式排序能力,其特點是具有分散式的效能可擴充套件性,但在單機的執行效率上有提升空間。而在 4.0 基於新的 SQL 執行框架進行分散式排序後,執行效能得到了大幅度提升。
二、結合 LSM-Tree 儲存架構展開最佳化
不同於傳統資料庫的 B+Tree 的原地更新儲存模型,LSM-Tree 儲存架構的特點是增量資料會更新到增量的資料來源 Memtable 中,不會對已寫入磁碟的資料(SSTable)進行原地更新,只有在 Compaction 時,才會寫入新的磁碟資料。這一特點為 OceanBase 加快 DDL 中的資料補全提供了極大的便利:一方面,對於加列這類操作,可以自然地做到 Instant DDL,只需要在 Compaction 的進行非同步地資料補全;另一方面,對於建索引這類需要實時補全資料的 DDL 操作,DDL 與 DML 會協調獲得一個補全資料的版本號,而該版本號之前的事務資料都已提交,可以直接將補全的資料寫入到 SSTable 中,將 DML 產生的增量資料直接寫入 Memtable 中,因此,建索引過程中的增量資料可以實時維護好,無需類似於原地更新儲存的追資料過程。
經過數年的發展迭代,OceanBase 使用者現在可以線上進行絕大部分 DDL 操作,包括:索引操作、列操作、生成列操作、外來鍵操作、表操作,分割槽操作。
功能分類 | 支援的操作 | 是否Online | 需要操作哪些資料 |
---|---|---|---|
新增索引 | 是 | Schema後設資料,索引表資料 | |
刪除索引 | 是 | Schema後設資料 | |
重新命名索引 | 是 | Schema後設資料 | |
列操作 | 新增列 | 是 | Schema後設資料,主表資料(Instant) |
刪除列 | 是 | Schema後設資料,主表資料(Instant) | |
重新命名列 | 是 | Schema後設資料 | |
重排列 | 是 | Schema後設資料 | |
設定列預設值 | 是 | Schema後設資料 | |
刪除列預設值 | 是 | Schema後設資料 | |
修改列型別 | 是 | Schema後設資料,主表資料(Instant) | |
設定列約束為NULL | 是 | Schema後設資料 | |
設定列約束為NOT NULL | 是 | Schema後設資料,查詢資料 | |
生成列操作 | 新增VIRTUAL 列 | 是 | Schema後設資料 |
刪除VIRTUAL 列 | 是 | Schema後設資料 | |
新增STORED 列 | 是 | Schema後設資料,主表資料(Instant) | |
刪除STORED 列 | 是 | Schema後設資料,主表資料(Instant) | |
外來鍵操作 | 新增外來鍵 | 是 | Schema後設資料,查詢表資料 |
刪除外來鍵 | 是 | Schema後設資料 | |
表操作 | 修改行格式 | 是 | Schema後設資料,主表資料(Instant) |
修改塊大小 | 是 | Schema後設資料,主表資料(Instant) | |
重新命名錶 | 是 | Schema後設資料 | |
修改壓縮演算法 | 是 | Schema後設資料,主表資料(Instant) | |
最佳化表空間 | 是 | Schema後設資料,主表資料(Instant) | |
分割槽操作 | 新增分割槽 | 是 | Schema後設資料 |
刪除分割槽 | 是 | Schema後設資料 | |
Truncate分割槽 | 是 | Schema後設資料,索引表資料(可選) |
表2 OceanBase 支援的 Online DDL 操作
來看看 4.0 DDL 有哪些新變化
▋ 伴隨業務成長的 DDL 新功能
在研發 4.0 之前,我們瞭解到一些使用者在新業務場景中需要經常對主鍵、分割槽等資料庫物件的結構進行變更,由於這類 DDL 操作需要將原表的資料進行重寫,我們稱這一類 DDL 變更為需要資料重整的 DDL。
什麼樣的場景會需要資料重整的 DDL 呢?
- 修改分割槽規則: 某業務在一開始的規模較小,在業務規模增長後,資料量或者負載超過單機的規模,而業務的查詢更新語句中,需要把某些列作為條件。此時需要按照這些列進行分割槽,將資料量或者負載打散到多個節點上;
- 修改字符集: 某業務剛開發時,誤將某一列的字符集 collation 設定大小寫不敏感,後來希望改造成大小寫敏感的;
- 修改列型別: 某業務表的列一開始定義為 int 列,後來業務發生變化,int 不足以表達業務的場景,需要修改為 varchar 列;
- 修改主鍵: 某業務的主鍵一開始使用業務自定義的 ID 列作為主鍵,後來希望使用自增列來作為主鍵。
使用者的業務成長不僅在於業務規模的增長,也在於業務對資料庫功能的使用更加深入。因此,DDL 功能也必須長期支撐業務發展,伴隨業務成長。我們發現,目前市面上的分散式資料庫並未能對這類 DDL 操作提供良好的支援,一是功能支援上不夠完善,例如部分資料庫並不支援主鍵變更、分割槽規則變更等;二是部分資料庫在做資料重整時會採用模擬使用者重新插入資料的方法,將資料重新匯出並插入一遍,效率較低並且可能與使用者事務產生衝突。
在 4.0 之前的版本,OceanBase 對於資料重整的 DDL,通常會採用手工資料遷移的方法完成變更。這種方法總體上有四個步驟:在原表的基礎上加入所做的 DDL 變更,建立出一張新的空表;將原表的資料匯出;將匯出後的資料寫入新表;將原表重新命名,並且將新表重新命名為老表。這一方法存在許多不足,如:操作步驟複雜;任務失敗時,需要藉助外部工具或者人工來回滾所做的操作;遷移效率低;遇到節點當機會提高冪等性的處理難度(比如無主鍵表場景)等。
4.0 能為使用者提供原生的資料重整 DDL 變更功能,一條 DDL 命令即可完成所有操作,包括修改分割槽規則、主鍵操作、修改列型別、修改字符集等,使用者無須關心 DDL 變更過程中出現的環境異常。
功能分類 | 支援的操作 |
---|---|
主鍵操作 | 新增主鍵 |
刪除主鍵 | |
修改主鍵 | |
列操作 | 修改列型別包括變短、變長、跨型別變更 |
修改自增列值 | |
新增自增列 | |
表操作 | 修改字符集修改表中所有已有資料的字符集 |
分割槽操作 | 重分割槽 非分割槽錶轉成一級、二級分割槽; 一級分割槽轉成其他一級或者二級分割槽;二級分割槽轉成一級分割槽或者其他二級分割槽; |
表3 4.0 DDL 新功能
為了支援好這些新功能,我們也對原生的 Online DDL 進行了擴充套件:
- 新增對多個依賴物件表格原子變更的能力;
- 大幅提升了原生 Online DDL 的資料補全效能;
- 旁路匯入產生的資料也適用高可用的資料同步;
- 對錶上的資料以及表的依賴物件的資料都進行資料一致性校驗,保證 DDL 變更產生的資料正確性。
▋ 原子變更保證資料同步更新
原子變更能夠保證使用者在執行完 DDL 後,如果 DDL 是成功的,那麼使用者之後都會看到新變更後的表結構和資料,如果 DDL 是失敗的,那麼使用者會看到變更之前的原表結構和資料。而進行需要重整資料的 DDL 變更會涉及到兩方面的操作,一方面,需要對錶上已有資料按照新的表格式進行修改;另一方面,需要對錶上的依賴物件按照新的表格式進行修改,如索引、約束、外來鍵和觸發器等。
另外,由於分散式資料庫中表上的資料可能分佈在不同的節點上,這也會帶來兩個問題:
- 如何原子地將分佈在不同節點的資料、多個依賴物件進行變更;
- 每個節點感知到最新變更後的表結構的時間會出現不同,如何保證在變更完成後,使用者只看到變更後的表結構。
為此,我們設計了一套分散式環境下表結構的變更流程,能保證原子地對資料、多個依賴物件進行變更,並且使用者在執行完 DDL 變更後,能夠使用最新的表結構來對錶格進行查詢、DML 等操作。
在實際業務場景中,進行 DDL 變更也有可能會碰到資料庫核心無法處理的異常。舉例來說,我們有一個列型別為 int 的表格,該列上有一個唯一索引,我們將這個列型別修改為 tinyint,如果表中有多行的該列的列值超過了 tinyint 表示的範圍,那麼這些行的所有列都會被截斷成 tinyint 的上界,導致多行在該列的列值有重複,不滿足唯一性約束。此時,有可能資料已完成部分的重寫,DDL 在碰到這類異常後主動回滾時,能夠保證使用者看到的是變更之前的原表的資料,而不是看到處於變更到一半的狀態。
▋ 並行能力提升資料補全速度
業界分散式資料庫普遍採用模擬使用者插入作為遷移資料的方式,該方案存在兩方面問題,一是可能會和正常業務產生行鎖衝突,二是由於方案中對事務的併發控制、記憶體索引結構的執行緒安全控制、以及實際寫入過程多次寫入的情況,其效能與傳統單機資料庫會存在明顯差距。
為了在設計層面降低 DDL 變更對業務的影響,同時讓 DDL 具有更高的執行效率,我們使用類似於 OceanBase 中索引建立採用的分散式排序、旁路寫入的方式將原表的資料遷移到新表。分散式排序方式節省事務、記憶體有序結構維護、多次compaction等邏輯,可以幫助使用者節省 CPU 開銷。而旁路寫入能夠避免資料寫入到 Memtable 中以及出現多次 compaction 的情況,節省記憶體和 IO 開銷。
OceanBase 4.0 基於新的並行執行框架,重新設計了 DDL 資料補全的分散式執行計劃,該計劃分為兩部分,第一部分是取樣和掃描運算元,第二部分是排序和掃描運算元。
圖1 OceanBase 4.0 DDL 資料補全分散式執行計劃
該方案能充分利用分散式和單機並行能力:
第一、兩個並行子計劃可能是執行在多臺機器上的,並且是新框架上同時排程起來的,能夠形成並行子計劃 1 往,並行子計劃 2 吐行並處理的流水線;
第二、為了防止分割槽間的資料傾斜,我們將每個分割槽拆分成多份由不同的 sort 運算元處理,每個 sort 運算元會處理多個分割槽的資料,再結合取樣劃分演算法,讓每個分割槽的資料劃分相對均衡,這樣就能保證任務間的排序是均衡的。
另一方面,在單機執行效率上,我們透過一些技術提升了效能。如:在補資料流程中儘量使用向量化來進行批處理、寫本機磁碟資料和網路同步資料並行化、更高效的取樣演算法、新框架的靜態資料引擎避免行的後設資料反覆複製等。上述最佳化對於所有需要補全資料的場景,包括建立索引、資料重整的 DDL 效能提升都是有幫助的。
▋ 更嚴苛的可用性要求
分散式排序和旁路寫入對資料補全的效能提升有很大的幫助,但同時會帶來一個問題:旁路寫入的資料如何同步到備副本和備庫?OceanBase 4.0 版本中支援將旁路寫入到 SSTable 的資料透過 PAXOS 同步到備副本和備庫,在備副本和備庫回放時,只需要將 SSTable 中宏塊的資料地址和後設資料資訊回放到記憶體狀態機中,這樣做有如下好處:
- 旁路寫入的資料也具備高可用能力,當少數派節點當機時,不影響 DDL 執行;
- 旁路寫入的資料是經過資料編碼和通用壓縮的,資料量一般比原表資料量少很多,資料同步效能好;
- 備庫和備副本的執行邏輯統一,不再依賴特殊程式碼邏輯來處理資料同步,程式碼更易於維護。
▋ 更全面的資料一致性校驗
在做資料重整的 DDL 時,需要將原表的資料遷移到新表,我們希望在 DDL 變更後,使用者的資料是正確無誤的。OceanBase 4.0 能夠透過一致性校驗保證 DDL 成功後,遷移資料和原表資料的一致性,即便 DDL 過程中發生了一些預期外異常導致資料不一致,OceanBase 也會回滾掉該 DDL 操作。展開來講,OceanBase 4.0 會校驗新表、新表上的索引、約束、外來鍵等所有物件,只有所有表中的資料,依賴物件上的資料都是一致的,DDL 操作才可能成功。
4.0 DDL 新功能上手實測
▋ 新功能測試
一、主鍵操作
主鍵操作
- 新增主鍵
OceanBase(admin@test)>create table t1(c1 int);
Query OK, 0 rows affected (0.18 sec)
--新增主鍵示例
OceanBase(admin@test)>alter table t1 add primary key(c1);
Query OK, 0 rows affected (1.28 sec)
--新增主鍵驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
PRIMARY KEY (`c1`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+-------------------------------------------------------+
1 row in set (0.03 sec)
- 刪除主鍵
OceanBase(admin@test)>create table t1(c1 int primary key);
Query OK, 0 rows affected (0.19 sec)
--刪除主鍵示例
OceanBase(admin@test)>alter table t1 drop primary key;
Query OK, 0 rows affected (1.39 sec)
--刪除主鍵驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+-------------------------------------------------------+
1 row in set (0.03 sec)
- 修改主鍵
OceanBase(admin@test)>create table t1(c1 int, c2 int primary key);
Query OK, 0 rows affected (0.18 sec)
--修改主鍵示例
OceanBase(admin@test)>alter table t1 drop primary key, add primary key(c2);
Query OK, 0 rows affected (1.38 sec)
--修改主鍵驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) NOT NULL,
PRIMARY KEY (`c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+-------------------------------------------------------+
1 row in set (0.03 sec)
修改分割槽規則
- 非分割槽錶轉成一級、二級分割槽
--非分割槽錶轉成一級分割槽示例
OceanBase(admin@test)>alter table t1 partition by hash(c1) partitions 4;
Query OK, 0 rows affected (1.51 sec)
--非分割槽錶轉成一級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) DEFAULT NULL,
`c2` datetime DEFAULT NULL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by hash(c1)
(partition p0,
partition p1,
partition p2,
partition p3) |
+-------+-------------------------------------------------------+
1 row in set (0.03 sec)
OceanBase(admin@test)>create table t1(c1 int, c2 datetime);
Query OK, 0 rows affected (0.18 sec)
--非分割槽錶轉換成二級分割槽示例
OceanBase(admin@test)>alter table t1 partition by range(c1) subpartition by key(c2) subpartitions 5 (partition p0 values less than(0), partition p1 values less than(100));
Query OK, 0 rows affected (1.96 sec)
--非分割槽錶轉換成二級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) DEFAULT NULL,
`c2` datetime DEFAULT NULL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by range(c1) subpartition by key(c2) subpartition template (
subpartition p0,
subpartition p1,
subpartition p2,
subpartition p3,
subpartition p4)
(partition p0 values less than (0),
partition p1 values less than (100)) |
+-------+-------------------------------------------------------+
1 row in set (0.04 sec)
- 一級分割槽轉成其他一級或者二級分割槽
OceanBase(admin@test)>create table t1(c1 int, c2 datetime, primary key(c1, c2))
-> partition by hash(c1) partitions 4;
Query OK, 0 rows affected (0.20 sec)
--一級分割槽轉換成其他一級分割槽示例
OceanBase(admin@test)>alter table t1 partition by key(c1) partitions 10;
Query OK, 0 rows affected (1.84 sec)
--一級分割槽轉換成其他一級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` datetime NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by key(c1)
(partition p0,
partition p1,
partition p2,
partition p3,
partition p4,
partition p5,
partition p6,
partition p7,
partition p8,
partition p9) |
+-------+--------------------------------------------------------+
1 row in set (0.03 sec)
OceanBase(admin@test)>create table t1(c1 int, c2 datetime, primary key(c1, c2))
-> partition by hash(c1) partitions 4;
Query OK, 0 rows affected (0.19 sec)
--一級分割槽轉換成二級分割槽示例
OceanBase(admin@test)>alter table t1 partition by range(c1) subpartition by key(c2) subpartitions 5 (partition p0 values less than(0), partition p1 values less than(100));
Query OK, 0 rows affected (1.88 sec)
--一級分割槽轉換成二級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` datetime NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by range(c1) subpartition by key(c2) subpartition template (
subpartition p0,
subpartition p1,
subpartition p2,
subpartition p3,
subpartition p4)
(partition p0 values less than (0),
partition p1 values less than (100)) |
+-------+-------------------------------------------------------+
1 row in set (0.03 sec)
- 二級分割槽轉成一級分割槽或者其他二級分割槽
OceanBase(admin@test)>create table t1(c1 int, c2 datetime, primary key(c1, c2)) partition by range(c1) subpartition by key(c2) subpartitions 5 (partition p0 values less than(0), partition p1 values less than(100));
Query OK, 0 rows affected (0.23 sec)
--二級分割槽轉換成一級分割槽示例
OceanBase(admin@test)>alter table t1 partition by key(c1) partitions 10;
Query OK, 0 rows affected (1.98 sec)
--二級分割槽轉換成一級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` datetime NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by key(c1)
(partition p0,
partition p1,
partition p2,
partition p3,
partition p4,
partition p5,
partition p6,
partition p7,
partition p8,
partition p9) |
+-------+--------------------------------------------------------+
1 row in set (0.03 sec)
OceanBase(admin@test)>create table t1(c1 int, c2 datetime, primary key(c1, c2)) partition by range(c1) subpartition by key(c2) subpartitions 5 (partition p0 values less than(0), partition p1 values less than(100));
Query OK, 0 rows affected (0.24 sec)
--二級分割槽轉換成其他二級分割槽示例
OceanBase(admin@test)>alter table t1 partition by hash(c1) subpartition by key(c2) subpartition template(subpartition sp0, subpartition sp1, subpartition sp2) PARTITIONS 5;
Query OK, 0 rows affected (2.07 sec)
--二級分割槽轉換成其他二級分割槽驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` datetime NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
partition by hash(c1) subpartition by key(c2) subpartition template (
subpartition sp0,
subpartition sp1,
subpartition sp2)
(partition p0,
partition p1,
partition p2,
partition p3,
partition p4) |
+-------+--------------------------------------------------------+
1 row in set (0.03 sec)
修改列型別
- 修改某列的列型別,包括變短、變長、跨型別變更、修改為自增列、修改字符集等等
OceanBase(admin@test)>create table t1(c1 varchar(32), c2 int, primary key(c1,c2));
Query OK, 0 rows affected (0.19 sec)
--修改列型別:變短示例
OceanBase(admin@test)>alter table t1 modify c1 varchar(16);
Query OK, 0 rows affected (1.47 sec)
--修改列型別:變短驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` varchar(16) NOT NULL,
`c2` int(11) NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.03 sec)
OceanBase(admin@test)>create table t1(c1 varchar(32), c2 int, primary key(c1,c2));
Query OK, 0 rows affected (0.17 sec)
--修改列型別:變長示例
OceanBase(admin@test)>alter table t1 modify c1 varchar(48);
Query OK, 0 rows affected (0.17 sec)
--修改列型別:變長驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` varchar(48) NOT NULL,
`c2` int(11) NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.01 sec)
OceanBase(admin@test)>create table t1(c1 int, c2 int, primary key(c1,c2));
Query OK, 0 rows affected (0.17 sec)
--修改列型別:跨型別變更示例
OceanBase(admin@test)>alter table t1 modify c1 varchar(48);
Query OK, 0 rows affected (1.38 sec)
--修改列型別:跨型別變更驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` varchar(48) NOT NULL,
`c2` int(11) NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.03 sec)
OceanBase(admin@test)>create table t1(c1 int, c2 int, primary key(c1,c2));
Query OK, 0 rows affected (0.18 sec)
--修改列型別:將某列修改為自增列示例
OceanBase(admin@test)>alter table t1 modify c1 int auto_increment;
Query OK, 0 rows affected (0.58 sec)
--修改列型別:將某列修改為自增列驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL AUTO_INCREMENT,
`c2` int(11) NOT NULL,
PRIMARY KEY (`c1`, `c2`)
) AUTO_INCREMENT = 1 AUTO_INCREMENT_MODE = 'ORDER' DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.01 sec)
OceanBase(admin@test)>create table t1 (c1 int, c2 varchar(32), c3 varchar(32), primary key (c1), unique key idx_test_collation_c2(c2));
Query OK, 0 rows affected (0.24 sec)
--修改列型別:修改某列的字符集示例
OceanBase(admin@test)>alter table t1 modify column c2 varchar(32) charset gbk;
Query OK, 0 rows affected (2.12 sec)
--修改列型別:修改某列的字符集驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` varchar(32) CHARACTER SET gbk DEFAULT NULL,
`c3` varchar(32) DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `idx_test_collation_c2` (`c2`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.05 sec)
修改字符集
- 修改表中所有已有資料的字符集
OceanBase(admin@test)>create table t1 (c1 int, c2 varchar(32), c3 varchar(32), primary key (c1), unique key idx_test_collation_c2(c2));
Query OK, 0 rows affected (0.23 sec)
--修改表中已有資料的字符集示例
OceanBase(admin@test)>alter table t1 CONVERT TO CHARACTER SET gbk COLLATE gbk_bin;
Query OK, 0 rows affected (2.00 sec)
--修改表中已有資料的字符集驗證
OceanBase(admin@test)>show create table t1;
+-------+-------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` varchar(32) COLLATE gbk_bin DEFAULT NULL,
`c3` varchar(32) COLLATE gbk_bin DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `idx_test_collation_c2` (`c2`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = gbk COLLATE = gbk_bin ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 2 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+--------------------------------------------------------+
1 row in set (0.05 sec)
▋ 效能測試
我們以建立索引為例進行測試,往表格中匯入一定行數的資料,測試建索引的時間,該時間主要消耗在索引資料補全,透過該測試可以評測出各個資料庫的資料補全效能。
實驗配置
表結構: create table t1(c1 int, c2 varchar(755)) partition by hash(c1) partitions 10;
資料量: 1000 萬行;
資源配置: 單機,並行度 10,排序記憶體 128M;
測試場景:
create index i1 on t1(c1) global;
create index i1 on t1(c2) global;
create index i1 on t1(c1,c2) global;
create index i1 on t1(c2,c1) global;
對比指標:建立索引的耗時,單位為 s;
對比產品:單機資料庫 MySQL、某分散式資料庫 A 以及 OceanBase 4.0。
效能測試結果
圖2 效能測試對比
如上圖所示,其中資料庫 A 是透過模擬插入的方式來進行補全的,測試結果顯示,OceanBase 索引構建效能,相對於資料庫 A,提升了 10-20 倍,相對於 MySQL,提升了 3-4 倍。因此,使用排序和旁路寫入方式來補全資料,能大幅提升索引構建效能。OceanBase 4.0 經過單機效能最佳化,資料補全的速度明顯快於 MySQL。
寫在最後
OceanBase 4.0 支援了常見的資料重整的 DDL 功能,包括修改主鍵、修改列型別、修改分割槽規則、修改字符集等。我們希望透過原子變更、更全面的資料一致性校驗和高可用的資料同步技術,讓使用者只需要執行一條語句就能完成所需要的變更操作,無須考慮分散式環境中出現的異常場景,減少業務對分散式環境的感知,使用上更加透明。同時,我們提升了 DDL 的單機和分散式並行能力,加快 DDL 中資料補全的速度,使得 DDL 執行更加高效。
我們期待 4.0 對 DDL 功能的完善和最佳化,可以幫助使用者從容應對多變的業務挑戰。