天天看點

OceanBase 2.2 版本在存儲過程功能上做了哪些改進和突破?基本功能完善協定層的擴充性能提升總結立即申請免費體驗OceanBase 2.2版本

OB君:好消息!「 OceanBase 2.2 版本 」正式上線官網啦!(點選閱讀原文即可直接下載下傳)OceanBase 2.2版本是成功支撐2019年天貓雙11大促的穩定版本,同時也是用于TPC-C測試且榮登TPC-C性能榜首的版本。本文為 “OceanBase 2.2 解析系列” 第二篇,該系列将從事務、SQL、相容性等次元為大家全面解讀2.2版本的功能和特性。歡迎持續關注!

OceanBase 從2.0版本開始支援了存儲過程(點選文章:“

OceanBase 2.0——第一款支援“存儲過程”的原生分布式資料庫

”回顧),并提供了MySQL和Oracle兩種相容模式。其中,2.0版本提供了較為完備的MySQL模式的存儲過程功能,但對Oracle模式的存儲過程功能(PL/SQL)支援還不夠完全。在OceanBase 2.2版本中,我們在存儲過程方面做了許多重大的改進和突破,本文将從基本功能完善、協定層的擴充和性能提升等方面介紹OceanBase 2.2版本在存儲過程方面取得的新進展。

基本功能完善

OceanBase 2.0版本提供了與Oracle相容的存儲過程功能,包括各種資料類型、聲明和定義語句、控制語句、異常處理等基本功能,在這些基本功能的基礎上,使用者可以完成各種基礎的程式設計功能。但是因為一些功能方面的限制,在使用上會不太友善,有時需要對業務代碼做一些改造才可以使用。一方面這些使用限制影響了OceanBase拓展市場的進展,另一方面也為我們進一步的發展指明了方向。我們根據業務的緊迫程度,迅速補充了大量業務中常用的功能點,提高産品的成熟度,主要有以下幾個方面的改進和增強:

  • 支援子過程和子類型
  • Collection類型的完善:在原來僅提供Nested Table一種Collection的基礎上,增加了Varray和Associative Array兩種類型;實作了first、last、prior、next、limit、exists方法,并完善了delete方法
  • 支援 %TYPE 與 %ROWTYPE
  • 遊标的完善:遊标變量、遊标的屬性、帶參數遊标等功能
  • 支援自治事務
  • 支援FORALL語句
  • 新增系統包:dmbs_random、dbms_lob、dbms_metadata、dbms_output、dbms_spm、utl_raw

通過以上功能的補充和完善,OceanBase 2.2版本已經完成了對Oracle存儲過程 90%以上的功能覆寫。在對外輸出中我們發現,使用者原本運作在Oracle上的存儲過程代碼絕大部分都可以無修改的在2.2版本的Oracle模式正常執行。

協定層的擴充

Oracle的PL/SQL提供了非常豐富的資料類型,這些資料類型不僅可以在PL/SQL内部調用中作為參數傳遞,還可以通過JDBC、ODBC等驅動和應用程式之間進行資料傳遞。OceanBase和驅動之間的通信協定基于MySQL協定開發,為了實作相容Oracle的協定,必須對原來的MySQL協定進行擴充。

複合資料類型傳輸協定

OceanBase 2.2版本為了支援和Oracle相容的複合資料類型參數傳輸功能,對MySQL的二進制協定進行了拓展,并解決了存儲過程在輸入輸出Oracle的複合資料類型( 其中包括 Record 、VARRAY 、NestedTable 、Associative Array )的時候,協定層和server進行資料交換時候的傳輸協定和序列化反序列化協定問題。

驅動擴充了MySQL Protocol Type,并增加了對複雜資料類型的set接口:setArray和setObject,根據複雜類型的序列化方法對資料進行序列化并發送給server。OceanBase對外提供相容Oracle的視圖ALL_TYPES、USER_COLL_TYPES、USER_TYPE_ATTRS、ALL_TYPE_ATTRS、ALL_COLL_TYPES等,驅動根據需要發送SQL去通路這些視圖拉取需要的META資訊,進而能夠反序列化server發送的資料。

複合資料類型傳輸協定的擴充,使得從OceanBase 2.2版本開始,使用者可以通過應用程式直接向server發送Array和Object資料對象,或者從server讀取Array和Object資料對象,大大降低了應用開發的複雜度,簡化了開發和維護的工作量,如 例1 為OceanBase 2.2版本的 TPC-C測試new order代碼。否則,應用需要傳輸Array或者Object時,必須把結構拆成多個的基礎資料類型,如 例2 為DBT2的 TPCC測試new order代碼。

CREATE OR REPLACE TYPE numarray IS TABLE OF NUMBER;
CREATE OR REPLACE TYPE distarray IS TABLE OF VARCHAR(24); 
CREATE OR REPLACE TYPE chararray IS TABLE OF VARCHAR(1); 

CREATE OR REPLACE PROCEDURE neworder (
  par_w_id INTEGER,
  par_d_id INTEGER,
  par_c_id INTEGER, 
  par_o_all_local INTEGER, 
  par_o_ol_cnt IN OUT BINARY_INTEGER, 
  par_w_tax OUT NUMBER, 
  par_d_tax OUT NUMBER, 
  par_o_id OUT INTEGER, 
  par_c_discount OUT NUMBER,
  par_c_credit OUT varchar2, 
  par_c_last OUT varchar2,
  par_retry IN OUT BINARY_INTEGER, 
  par_cr_date DATE,
  par_ol_i_id intarray, 
  par_ol_supply_w_id intarray, 
  par_i_price OUT numarray, 
  par_i_name OUT distarray, 
  par_s_quantity OUT intarray, 
  par_brand_generic OUT chararray, 
  par_ol_amount OUT numarray, 
  par_s_remote intarray, 
  par_ol_quantity intarray
) 
IS BEGIN
 ……
END;           

例2:

d_id INT,           
                           c_id INT,           
                           o_all_local INT,
                           o_ol_cnt INT,           
                           ol_i_id1 INT,           
                           ol_supply_w_id1 INT,           
                           ol_quantity1 INT,           
                           ol_i_id2 INT,           
                           ol_supply_w_id2 INT,           
                           ol_quantity2 INT,           
                           ol_i_id3 INT,           
                           ol_supply_w_id3 INT,           
                           ol_quantity3 INT,           
                           ol_i_id4 INT,           
                           ol_supply_w_id4 INT,           
                           ol_quantity4 INT,           
                           ol_i_id5 INT,           
                           ol_supply_w_id5 INT,           
                           ol_quantity5 INT,           
                           ol_i_id6 INT,           
                           ol_supply_w_id6 INT,           
                           ol_quantity6 INT,           
                           ol_i_id7 INT,           
                           ol_supply_w_id7 INT,           
                           ol_quantity7 INT,           
                           ol_i_id8 INT,           
                           ol_supply_w_id8 INT,           
                           ol_quantity8 INT,           
                           ol_i_id9 INT,           
                           ol_supply_w_id9 INT,           
                           ol_quantity9 INT,           
                           ol_i_id10 INT,           
                           ol_supply_w_id10 INT,           
                           ol_quantity10 INT,           
                           ol_i_id11 INT,           
                           ol_supply_w_id11 INT,           
                           ol_quantity11 INT,           
                           ol_i_id12 INT,           
                           ol_supply_w_id12 INT,           
                           ol_quantity12 INT,           
                           ol_i_id13 INT,           
                           ol_supply_w_id13 INT,           
                           ol_quantity13 INT,           
                           ol_i_id14 INT,           
                           ol_supply_w_id14 INT,           
                           ol_quantity14 INT,           
                           ol_i_id15 INT,           
                           ol_supply_w_id15 INT,           
                           ol_quantity15 INT,
                           out rc int)
IS BEGIN
 ……
END;
/           

服務端遊标傳輸協定

Oracle可以使用REF CURSOR作為輸入和輸出參數,在procedure之間,以及在server和client之間進行資料傳遞。Oracle的服務端遊标指向一個結果集的指針,在OPEN的時候生成該結果集,如有必要會同時鎖行。傳回給用戶端CURSOR的唯一辨別ID,用戶端通過該ID進行服務端的FETCH和CLOSE,以及本地的緩存。而MySQL通過結果集在服務端的物化實作服務端遊标,不支援基于遊标的更新和滾動。

OceanBase 2.2的Oracle模式實作了服務端遊标功能。服務端遊标的主要作用是可以實作Client從server按需擷取資料,減少資料通信量,并簡化應用程式開發,如 例3 所示。而在支援這一協定前,應用隻能通過Array擷取SQL語句的所有結果集,當結果集非常大的時候,不僅對網絡造成巨大壓力,而且可能會造成驅動的記憶體耗盡。

例3:

CREATE OR REPLACE PROCEDURE emp_cursor(p_cursor OUT sys_refcursor)
IS
BEGIN
    open p_cursor for select * from test_emp;
END;
/

JAVA代碼:CallableStatement csmt = conn.prepareCall("{call emp_cursor(?)}");
csmt.registerOutParameter(1, OracleTypes.CURSOR);
csmt.execute();
ResultSet resultSet = (ResultSet) csmt.getObject(1);
while (resultSet.next()) {
  int columnCnt = resultSet.getMetaData().getColumnCount();
  for (int j = 1; j <= columnCnt; j++) {
    System.out.println(resultSet.getMetaData().getColumnName(j) + ":"
                                           + resultSet.getString(j));
  }
}           

OceanBase 的server在打開遊标時會生成result set,對于作為輸出參數的REF CURSOR,該結果集存放在session上,通過ID通路遊标的結果集,session斷開時統一釋放掉。結果集裡面包含了遊标對應語句的實體執行計劃和執行上下文,在遊标關閉之前所持有的資源不會釋放,并通過SQL層的get_next()方法來實作FETCH功能。

驅動通過getCursor(int)方法,可以擷取server端的遊标資訊,而在每次Fetch的時候驅動并不會每次都跟server進行互動——如果本地有資料緩存,從本地直接傳回,沒有緩存的時候才會去互動一次預取一批。

性能提升

在OceanBase 2.2版本中,存儲過程進行了大量的性能方面的優化,以TPC-C測試為比較基準的話,同上一版本相比,存儲過程所做的優化為TPC-C帶來了約30%的性能提升。

Array Binding

Array Binding是對DML語句進行批量處理的能力,這一優化在TPC-C測試中發揮了重要的作用。一條SQL在資料庫中的執行過程大緻可以分為“計劃生成”和“執行”兩個階段,盡管我們對SQL的執行計劃做了高速緩存,但找到一個合适的執行計劃在整個執行過程中仍然是比較耗時的一個部分。當一組SQL的執行計劃完全一樣而隻有執行期參數不同是,在存儲過程中我們可以通過特定的文法将他們的執行做成一個批量處理的過程,此時“計劃生成”隻需要做一次即可。在Array Binding中,資料庫首先找到需要使用的計劃,然後執行該計劃,并在每次執行完畢後,重新執行參數綁定(Binding)的過程。

Array Binding這一性能優化是通過存儲過程中使用FORALL語句進行觸發的。使用FORALL不僅可以優化SQL語句的計劃生成,還大幅減少了存儲過程引擎和SQL引擎之間的互動次數。在Client和Server之間的互動通過存儲過程被大幅減少之後,存儲過程引擎和SQL引擎之間互動的減少帶來的性能提升十分可觀。如 例4 所示是TPC-C測試中delivery事務的實作代碼,使用了FORALL後,原本幾十次的存儲過程引擎和SQL引擎之間互動、以及SQL語句的計劃生成和查找過程被減少到了4次。

例4:

ware_id IN INTEGER,
  dist_id OUT intarray,
  order_id OUT intarray,
  ordcnt OUT INTEGER,
  sums OUT numarray,
  del_date IN DATE,
  carrier_id IN INTEGER,
  order_c_id OUT intarray,
  retry IN OUT BINARY_INTEGER
)
IS
  TYPE int_array IS TABLE OF BINARY_INTEGER;
  var_dist int_array := int_array();

  not_serializable EXCEPTION;
  PRAGMA EXCEPTION_INIT(not_serializable,-6235); 

BEGIN

  var_dist.EXTEND(10);
  FOR var_x IN 1..10 LOOP
    var_dist(var_x) := var_x;
  END LOOP;

  LOOP
    BEGIN
      ordcnt := 0;
      IF dist_id.count != 0 THEN
        dist_id.delete;
        order_id.delete;
      END IF;

      FORALL IDX IN 1..10
        DELETE FROM nord
          WHERE no_w_id = ware_id AND no_d_id = var_dist(IDX) AND no_o_id =
            (SELECT no_o_id FROM nord
              WHERE no_d_id = var_dist(IDX) AND no_w_id = ware_id AND rownum <= 1)
          RETURNING no_d_id, no_o_id BULK COLLECT INTO dist_id, order_id;
  
      ordcnt := SQL%ROWCOUNT;

      FORALL o in 1.. ordcnt
        UPDATE ordr SET o_carrier_id = carrier_id WHERE o_w_id = ware_id 
            AND o_d_id = dist_id(o)
            AND o_id = order_id(o)
            RETURNING o_c_id BULK COLLECT INTO order_c_id;

      FORALL o in 1.. ordcnt
        UPDATE ordl SET ol_delivery_d = del_date WHERE ol_w_id = ware_id
            AND ol_d_id = dist_id(o)
            AND ol_o_id = order_id(o)
            RETURNING sum(ol_amount) BULK COLLECT INTO sums;

      FORALL c IN 1.. ordcnt
        UPDATE cust
            SET c_balance = c_balance + sums(c), c_delivery_cnt = c_delivery_cnt + 1
            WHERE c_w_id = ware_id AND c_d_id = dist_id(c) AND c_id = order_c_id(c);

      COMMIT;

      /* No exceptions, exit*/
      EXIT;

      EXCEPTION
        WHEN not_serializable THEN
          BEGIN
            ROLLBACK;
            retry := retry + 1;
          END;
    END;      
  END LOOP; 

END delivery;
/                                                          

Prepared Statement協定優化

Prepared Statement是一種二進制的請求交換協定,可以大大降低系統的互動成本。OceanBase 2.2版本不僅支援使用者程式與資料庫之間使用Prepared Statement協定執行SQL和存儲過程,同時在存儲過程引擎和SQL引擎之間的調用中使用了這一互動方式。存儲過程引擎在編譯的時候會對每一條SQL進行Prepare操作并擷取唯一的ID,後續每次在執行的時候通過傳入該ID和對應的參數,系統就可以通過高速緩存找到對應的該SQL的執行計劃開始執行。相對于文本協定,這一過程省去了大部分的文本解析開銷,性能獲得大幅提升。

表達式計算優化

因為采用了基于LLVM的編譯執行實作存儲過程基礎架構,存儲過程中的性能消耗主要發生在表達式計算和SQL執行上,真正存儲過程控制邏輯的占比非常少。是以表達式計算是性能優化的重要方向。

為了實作更好的代碼可維護性,存儲過程的表達式計算采用的是同SQL一樣的表達式計算引擎,也就是說所有存儲過程需要計算表達式的時候,會通過調用SQL引擎完成。這裡除了本身大量的調用開銷之外,SQL引擎的表達式計算架構采用的是基于遞歸的中綴計算架構。存儲過程裡存在大量的整數比較、自加自減、數值初始化等操作,這些表達式的計算非常簡單,走一遍執行一遍SQL引擎的表達式計算流程非常耗時,嚴重影響了存儲過程控制邏輯的運作速度。為此我們對這種簡單表達式實作了一套簡單的基于LLVM的計算架構,并内嵌在存儲過程的執行引擎當中,一方面避免了對SQL引擎的額外調用,另一方面大幅提升了表達式計算效率。根據測試,僅這一優化就使得循環控制邏輯的性能提升百倍以上。

TCL處理優化

TCL是指事務控制語句,在存儲過程裡主要是COMMIT、ROLLBACK。為了實作更高的性能,2.2改掉了原本通過SQL引擎執行TCL語句的方式,而是直接通過存儲過程引擎調用事務層接口實作,省掉了中間十幾層的調用棧消耗。

另外,在一些特殊場景,存儲過程裡的TCL語句可以進行異步送出,工作線程在執行到TCL語句時,不必等待事務送出完成,而是把事務送出任務交給異步線程完成,自己去接受新的請求。

總結

OceanBase 2.2版本是全面支援Oracle PL/SQL的版本,其中的存儲過程功能除了在TPC-C測試中發揮重大作用之外,在内部業務中也已經上線,經曆了雙11極緻場景的考驗,而且已經輸出到了多家外部客戶業務。

随着OceanBase的産品越來越成熟,存儲過程将在OceanBase商業化程序中發揮越來越大的作用。盡管2.2版本的存儲過程功能相比之前已經有了長足進步,但相比Oracle豐富的PL/SQL還存在很多不完善的地方,後續版本正在迅速彌補這些短闆,尤其是下一階段,我們将支援更多相容Oracle的内建系統包,配合存儲過程的功能支援,為使用者業務的平滑遷移提供有力的支援。

立即申請免費體驗OceanBase 2.2版本

「 OceanBase 2.2 版本 」正式上線官網啦!OceanBase 2.2版本是成功支撐2019年天貓雙11大促的穩定版本,同時也是用于TPC-C測試且榮登TPC-C性能榜首的版本。OceanBase 2.2版本除了在螞蟻金服和網商銀行廣泛使用外,目前也在部分金融機構中使用。

想要立即體驗「OceanBase 2.2版本」?

免費擷取連結:

https://oceanbase.alipay.com/download/resource

如果你在安裝和使用的過程中遇到問題且希望跟OceanBase一線專家進行技術交流,您加入OceanBase技術交流釘釘群,打開釘釘搜尋群号:21949783(備注:OB 2.2)

我們非常重視來自每一位開發者使用者的體驗和心得,希望能夠獲得你們的寶貴回報。