朱 濤, 周敏奇, 張 召
(華東師范大學(xué) 軟件學(xué)院, 上海 200062)
?
面向OceanBase的存儲(chǔ)過(guò)程實(shí)現(xiàn)技術(shù)研究
朱 濤, 周敏奇, 張 召
(華東師范大學(xué) 軟件學(xué)院, 上海 200062)
存儲(chǔ)過(guò)程是一段被命名后保存在數(shù)據(jù)庫(kù)服務(wù)器端,并預(yù)先編譯好的代碼,可以減少前臺(tái)應(yīng)用程序和后臺(tái)數(shù)據(jù)庫(kù)間的網(wǎng)絡(luò)傳輸量. 本文主要研究基于靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言,兩種典型的存儲(chǔ)過(guò)程實(shí)現(xiàn)方法,來(lái)闡述存儲(chǔ)過(guò)程實(shí)現(xiàn)的基本原理. 并基于此,進(jìn)一步探討了在OceanBase主存數(shù)據(jù)庫(kù)服務(wù)器中添加存儲(chǔ)過(guò)程模塊的解決方案和技術(shù)難點(diǎn).
存儲(chǔ)過(guò)程; 數(shù)據(jù)庫(kù)系統(tǒng); SQL
在各類以數(shù)據(jù)管理為核心的應(yīng)用中,數(shù)據(jù)庫(kù)通常扮演著底層存儲(chǔ)的角色.應(yīng)用服務(wù)器通過(guò)網(wǎng)絡(luò)通信與數(shù)據(jù)庫(kù)服務(wù)器進(jìn)行交互,當(dāng)某些任務(wù)需要多次訪問(wèn)數(shù)據(jù)庫(kù),應(yīng)用服務(wù)器需要與數(shù)據(jù)庫(kù)服務(wù)器進(jìn)行多次的網(wǎng)絡(luò)通信,并傳送大量的中間結(jié)果. 這會(huì)影響應(yīng)用對(duì)用戶查詢的響應(yīng)速度. 為了解決這一問(wèn)題,當(dāng)前主流數(shù)據(jù)庫(kù) ,如Oracle, Mysql, DB2等,均允許用戶在數(shù)據(jù)庫(kù)服務(wù)器端編寫程序來(lái)實(shí)現(xiàn)本在應(yīng)用程序段完成的業(yè)務(wù)邏輯. 這段被命名后預(yù)先編譯存儲(chǔ)在數(shù)據(jù)庫(kù)服務(wù)器端的代碼即為存儲(chǔ)過(guò)程[1].
1.1 存儲(chǔ)過(guò)程簡(jiǎn)介
存儲(chǔ)過(guò)程是大型數(shù)據(jù)庫(kù)系統(tǒng)中,一組完成特定功能的程序集,經(jīng)編譯后存儲(chǔ)在數(shù)據(jù)庫(kù)中,上層應(yīng)用調(diào)用存儲(chǔ)過(guò)程只需要給出存儲(chǔ)過(guò)程名和相應(yīng)的參數(shù)即可. 這樣可以減少應(yīng)用程序與數(shù)據(jù)庫(kù)間的數(shù)據(jù)交互次數(shù)和數(shù)據(jù)傳輸量. 因此,在一個(gè)以數(shù)據(jù)庫(kù)為核心的應(yīng)用系統(tǒng)中,需要跟數(shù)據(jù)庫(kù)頻繁交互的業(yè)務(wù)邏輯往往用存儲(chǔ)過(guò)程來(lái)完成.
總的來(lái)說(shuō),存儲(chǔ)過(guò)程具有以下三個(gè)優(yōu)點(diǎn):
(1) 預(yù)先編譯,運(yùn)行速度快. 如果跟數(shù)據(jù)庫(kù)的交互工作是在前臺(tái)的程序設(shè)計(jì)語(yǔ)言中完成,那么,每次跟數(shù)據(jù)庫(kù)連接,程序中相應(yīng)的SQL語(yǔ)句就要編譯一次,而存儲(chǔ)過(guò)程卻可以一次編譯多次運(yùn)行.
(2) 前臺(tái)應(yīng)用程序和數(shù)據(jù)庫(kù)服務(wù)器之間只傳存儲(chǔ)過(guò)程名稱和參數(shù),網(wǎng)絡(luò)通信量少. 使用存儲(chǔ)過(guò)程能在數(shù)據(jù)庫(kù)服務(wù)器上完成全部對(duì)數(shù)據(jù)庫(kù)的操作,只需返回最終結(jié)果. 從而減少了數(shù)據(jù)庫(kù)服務(wù)器與應(yīng)用程序之間的交互次數(shù)和數(shù)據(jù)傳輸總量.
(3) 對(duì)數(shù)據(jù)庫(kù)訪問(wèn)封裝,安全性高. 參數(shù)化的存儲(chǔ)過(guò)程可以防止SQL語(yǔ)句的注入式攻擊,并且可以將使用Grant、Deny以及Revoke等語(yǔ)句來(lái)完成=存儲(chǔ)過(guò)程級(jí)別的權(quán)限管理.
然而,存儲(chǔ)過(guò)程除了以上的三個(gè)優(yōu)點(diǎn)以外,也存在以下的三個(gè)缺點(diǎn):
(1) 程序調(diào)試?yán)щy. 由于存儲(chǔ)過(guò)程是運(yùn)行在數(shù)據(jù)庫(kù)端的一段代碼,并基于數(shù)據(jù)庫(kù)提供的獨(dú)特的編程語(yǔ)言來(lái)實(shí)現(xiàn). 導(dǎo)致常用的調(diào)試工具不能直接用來(lái)調(diào)試這類代碼. 因此,在開(kāi)發(fā)存儲(chǔ)過(guò)程時(shí),通常面臨調(diào)試?yán)щy的問(wèn)題.
(2) 代碼移植困難. 不像SQL語(yǔ)言,存儲(chǔ)過(guò)程語(yǔ)言并沒(méi)有被標(biāo)準(zhǔn)化. 不同的數(shù)據(jù)庫(kù)支持存儲(chǔ)過(guò)程的開(kāi)發(fā)語(yǔ)言都不盡相同. 當(dāng)Web應(yīng)用使用的數(shù)據(jù)庫(kù)系統(tǒng)發(fā)生變化時(shí),在兩個(gè)數(shù)據(jù)庫(kù)系統(tǒng)上移植相應(yīng)的存儲(chǔ)過(guò)程需要重新開(kāi)發(fā)和調(diào)試.
(3) 數(shù)據(jù)庫(kù)負(fù)擔(dān)重. 存儲(chǔ)過(guò)程雖然減少了應(yīng)用和數(shù)據(jù)庫(kù)之間的交互,但同時(shí)增加了數(shù)據(jù)庫(kù)的計(jì)算負(fù)擔(dān). 例如,原本對(duì)查詢結(jié)果集的遍歷訪問(wèn)是在應(yīng)用層完成的,數(shù)據(jù)庫(kù)只需要提供相應(yīng)的查詢結(jié)果即可. 但在存儲(chǔ)過(guò)程中,數(shù)據(jù)庫(kù)服務(wù)器不僅要提供查詢結(jié)果,還需要完成遍歷訪問(wèn)以及一些必要的數(shù)據(jù)加工和計(jì)算,并產(chǎn)生最終的結(jié)果返回給上層應(yīng)用.
1.2 已有數(shù)據(jù)庫(kù)系統(tǒng)對(duì)存儲(chǔ)過(guò)程的支持
雖然主流的數(shù)據(jù)庫(kù)管理系統(tǒng)都支持存儲(chǔ)過(guò)程,但是它們支持的語(yǔ)法和使用的方法卻有很大區(qū)別. 這是因?yàn)榇鎯?chǔ)過(guò)程還沒(méi)有形成統(tǒng)一的規(guī)范,并且存儲(chǔ)過(guò)程在各個(gè)系統(tǒng)中的實(shí)現(xiàn)也受限于系統(tǒng)本身的設(shè)計(jì). 表1列舉了當(dāng)前一些數(shù)據(jù)庫(kù)支持的存儲(chǔ)過(guò)程語(yǔ)言,其中的數(shù)據(jù)來(lái)源于各自的網(wǎng)站. 從中不難看出,不同數(shù)據(jù)庫(kù)所采用的存儲(chǔ)過(guò)程語(yǔ)言大不一樣. 即便是被多個(gè)數(shù)據(jù)庫(kù)同時(shí)采用的Java語(yǔ)言,其實(shí)際的存儲(chǔ)過(guò)程編程接口還是會(huì)有很大區(qū)別.
表1 主流數(shù)據(jù)庫(kù)系統(tǒng)存儲(chǔ)過(guò)程開(kāi)發(fā)語(yǔ)言
事實(shí)上,根據(jù)所使用的開(kāi)發(fā)語(yǔ)言的性質(zhì),存儲(chǔ)過(guò)程的實(shí)現(xiàn)可以分為兩類:(1)采用靜態(tài)過(guò)程語(yǔ)言實(shí)現(xiàn),例如SQL PL,PSQL,SPL,SQL/PSM,PL/SQL[2]等;(2)采用已有的支持反射機(jī)制的動(dòng)態(tài)編程語(yǔ)言實(shí)現(xiàn),例如Java,.Net Framework語(yǔ)言. 在第3節(jié)和第4節(jié),我們將以兩個(gè)分別使用靜態(tài)過(guò)程語(yǔ)言和已有動(dòng)態(tài)編程語(yǔ)言的開(kāi)源數(shù)據(jù)庫(kù)中存儲(chǔ)過(guò)程的實(shí)現(xiàn)來(lái)分別分析這兩類實(shí)現(xiàn)方案.
存儲(chǔ)過(guò)程是一段運(yùn)行在數(shù)據(jù)庫(kù)端的程序代碼,能夠被數(shù)據(jù)庫(kù)系統(tǒng)動(dòng)態(tài)地加載,編譯和執(zhí)行. 它支持?jǐn)?shù)據(jù)庫(kù)訪問(wèn),變量定義,流程控制等功能. 一個(gè)數(shù)據(jù)庫(kù)管理系統(tǒng)如果需要實(shí)現(xiàn)存儲(chǔ)過(guò)程功能,需要考慮以下幾個(gè)問(wèn)題.
2.1 基本語(yǔ)法
存儲(chǔ)過(guò)程需要支持的功能主要包括,變量聲明、變量賦值、表達(dá)式計(jì)算、流程控制以及SQL語(yǔ)句執(zhí)行,當(dāng)然,也需要包括為了解決數(shù)據(jù)庫(kù)和程序設(shè)計(jì)語(yǔ)言之間阻抗不匹配而引入的游標(biāo). 下面將對(duì)以上功能進(jìn)行逐一闡述.
變量聲明 存儲(chǔ)過(guò)程支持的變量包括數(shù)據(jù)庫(kù)本身支持的變量類型,以及表的行記錄,游標(biāo)等. 例如integer,numeric(5,2),varchar,tablename%ROWTYPE和cursor等. 在執(zhí)行存儲(chǔ)過(guò)程時(shí),變量的取值,變量的類型等信息需要存儲(chǔ)在過(guò)程的執(zhí)行上下文中.
代碼塊定義 代碼塊可以用于邏輯分組或者把變量局部化為作用于一個(gè)比較小的語(yǔ)句組,在塊內(nèi)能定義臨時(shí)變量,執(zhí)行若干語(yǔ)句等.
表達(dá)式計(jì)算表達(dá)式計(jì)算需要處理包含常量和變量的表達(dá)式,需要產(chǎn)生正確的結(jié)果及結(jié)果類型. 表達(dá)式計(jì)算可以有單獨(dú)的功能模塊支持,也可以直接繼承數(shù)據(jù)庫(kù)提供的表達(dá)式計(jì)算功能.
變量賦值 變量賦值要求除了能夠處理一般的數(shù)據(jù)類型,例如integer, varchar等,同時(shí)也要支持復(fù)合類型的賦值,如關(guān)系表中的行記錄. 當(dāng)然,變量賦值也應(yīng)支持?jǐn)?shù)據(jù)庫(kù)中特有的游標(biāo)類型的賦值.
流程控制 流程控制是提供編寫復(fù)雜的業(yè)務(wù)邏輯所必須的功能. 主要包括條件語(yǔ)句、循環(huán)語(yǔ)句以及過(guò)程返回語(yǔ)句. 其中,循環(huán)語(yǔ)句需要支持對(duì)關(guān)系表記錄的迭代.
數(shù)據(jù)庫(kù)訪問(wèn) 其中包括執(zhí)行SQL語(yǔ)句,使用游標(biāo)等. 在這個(gè)模塊,存儲(chǔ)過(guò)程需要能夠向主數(shù)據(jù)庫(kù)引擎提交SQL查詢,緩存SQL解析后的執(zhí)行計(jì)劃,定義和使用游標(biāo),將查詢結(jié)果存儲(chǔ)到變量中. 這個(gè)模塊通常需要設(shè)計(jì)數(shù)據(jù)庫(kù)的內(nèi)部接口,供服務(wù)器端編程使用.
2.2 過(guò)程編譯、執(zhí)行與存儲(chǔ)
存儲(chǔ)過(guò)程的編譯[6]發(fā)生在過(guò)程初次編譯階段和過(guò)程執(zhí)行階段. 這兩部分工作分別是由過(guò)程編譯模塊和SQL編譯模塊完成. 前者主要負(fù)責(zé)諸如變量定義賦值,流程控制等內(nèi)容的編譯工作,而后者負(fù)責(zé)SQL語(yǔ)句的編譯工作. 通常,這部分工作是在存儲(chǔ)過(guò)程被首次調(diào)用時(shí)完成的. 例如當(dāng)首次執(zhí)行SQL語(yǔ)句時(shí),數(shù)據(jù)庫(kù)內(nèi)核會(huì)完成對(duì)SQL的解析,并產(chǎn)生執(zhí)行計(jì)劃. 這部分編譯結(jié)果會(huì)由存儲(chǔ)過(guò)程模塊緩存.
過(guò)程的執(zhí)行同樣可以分為兩個(gè)部分:(1)由存儲(chǔ)過(guò)程模塊執(zhí)行的語(yǔ)句;(2)由數(shù)據(jù)庫(kù)引擎執(zhí)行的語(yǔ)句. 前者需要定義和實(shí)現(xiàn)每類語(yǔ)句的執(zhí)行函數(shù),完成語(yǔ)句的執(zhí)行. 后者需要定義訪問(wèn)數(shù)據(jù)庫(kù)的接口,通過(guò)訪問(wèn)接口來(lái)完成語(yǔ)句執(zhí)行. 返回的結(jié)果需要復(fù)制到存儲(chǔ)過(guò)程模塊執(zhí)行的上下文中,并交由該模塊進(jìn)行下一步的處理.
與保存在數(shù)據(jù)庫(kù)中其他信息一樣,數(shù)據(jù)庫(kù)管理系統(tǒng)也要保證存儲(chǔ)過(guò)程在系統(tǒng)故障恢復(fù)后,依然可以正常使用. 因此,如何將過(guò)程序列化到非易失性存儲(chǔ)器尤為重要. 根據(jù)采用的過(guò)程語(yǔ)言類型,當(dāng)前主要有兩種存儲(chǔ)方案. 其中,第一種方案是,存儲(chǔ)過(guò)程的源代碼會(huì)存儲(chǔ)在系統(tǒng)表中,編譯后的目標(biāo)代碼存儲(chǔ)在系統(tǒng)緩存中. 第二種方案是,在系統(tǒng)表中只記錄存儲(chǔ)過(guò)程的調(diào)用信息,例如過(guò)程名,參數(shù)信息等,而不存儲(chǔ)過(guò)程源代碼,編譯后的結(jié)果存儲(chǔ)在系統(tǒng)外部,當(dāng)需要調(diào)用時(shí),系統(tǒng)根據(jù)存儲(chǔ)路徑加載,并在系統(tǒng)內(nèi)部緩存.
在下一節(jié)中,我們將以PostgreSQL為例,重點(diǎn)介紹設(shè)計(jì)自定義過(guò)程語(yǔ)言實(shí)現(xiàn)存儲(chǔ)過(guò)程模塊的機(jī)制.
PostgreSQL[5,7]是一個(gè)自由的對(duì)象-關(guān)系數(shù)據(jù)庫(kù)服務(wù)器(數(shù)據(jù)庫(kù)管理系統(tǒng)),它在靈活的 BSD-風(fēng)格許可證下發(fā)行. 它提供了相對(duì)其他開(kāi)放源代碼數(shù)據(jù)庫(kù)系統(tǒng)(比如 MySQL 和 Firebird),和專有商用系統(tǒng)(比如 Oracle、Sybase、IBM 的 DB2 和 Microsoft SQL Server)之外的另一種選擇.
PostgreSQL雖然支持使用內(nèi)置過(guò)程語(yǔ)言PL/pgSQL、腳本語(yǔ)言、編譯語(yǔ)言C和C++等編寫存儲(chǔ)過(guò)程. 但本節(jié)重點(diǎn)以PostgreSQL為例,闡述靜態(tài)語(yǔ)言存儲(chǔ)過(guò)程實(shí)現(xiàn)的原理和機(jī)制.
3.1 PL/pgSQL簡(jiǎn)介
PL/pgSQL是PostgreSQL數(shù)據(jù)庫(kù)系統(tǒng)的一個(gè)可裝載的過(guò)程語(yǔ)言,其語(yǔ)法類似于Oracle的PL/SQL. PL/pgSQL的設(shè)計(jì)目標(biāo)是創(chuàng)建一種可裝載的過(guò)程語(yǔ)言:(1)可用于創(chuàng)建函數(shù)和觸發(fā)器過(guò)程;(2)為 SQL 語(yǔ)言增加控制結(jié)構(gòu);(3)可以執(zhí)行復(fù)雜的計(jì)算; (4)繼承所有用戶定義類型,函數(shù)和操作符.
為了支持PL/pgSQL,Postgre需要提供該過(guò)程語(yǔ)言的編譯功能、執(zhí)行功能、存儲(chǔ)和數(shù)據(jù)庫(kù)訪問(wèn)功能.
3.2 編譯與執(zhí)行
3.2.1 詞法解析與語(yǔ)法解析
在存儲(chǔ)過(guò)程的編譯階段需要將過(guò)程源代碼編譯為系統(tǒng)內(nèi)部指令樹(shù). 但需要注意的是,在函數(shù)內(nèi)使用到的獨(dú)立的SQL 表達(dá)式和 SQL 命令的編譯則是在過(guò)程首次調(diào)用階段完成,其由PostgreSQL的SQL解析模塊負(fù)責(zé)編譯,并產(chǎn)生執(zhí)行計(jì)劃.
正如傳統(tǒng)的SQL一樣,對(duì)過(guò)程源碼的編譯也需要經(jīng)過(guò)詞法解析和語(yǔ)法解析. 在PostgreSQL中采用開(kāi)源軟件Flex與Bison完成詞法解析與語(yǔ)法解析. Flex進(jìn)行詞法分析,這需要我們?cè)O(shè)計(jì)過(guò)程語(yǔ)言的語(yǔ)法后,為其設(shè)計(jì)正則表達(dá)式來(lái)匹配源碼中出現(xiàn)的標(biāo)記(常量,數(shù)學(xué)符號(hào),括號(hào),中括號(hào),變量名,保留字等),并定義規(guī)則將標(biāo)記映射為內(nèi)部標(biāo)志符. 之后,Bison來(lái)對(duì)詞法分析后的內(nèi)部標(biāo)志符序列進(jìn)行語(yǔ)法分析,形成抽象語(yǔ)義樹(shù). 我們需要為每個(gè)語(yǔ)義單元設(shè)計(jì)識(shí)別規(guī)則,并為其創(chuàng)建一個(gè)相應(yīng)的結(jié)構(gòu)保存.
3.2.2 過(guò)程執(zhí)行
過(guò)程的執(zhí)行實(shí)際上是一個(gè)遍歷語(yǔ)法樹(shù)的過(guò)程,根據(jù)當(dāng)前訪問(wèn)的指令,轉(zhuǎn)到對(duì)應(yīng)的指令函數(shù)進(jìn)行執(zhí)行. 圖1以IF語(yǔ)句為例,來(lái)說(shuō)明這個(gè)過(guò)程.
圖1 IF語(yǔ)句的指令函數(shù)
對(duì)于每個(gè)語(yǔ)法樹(shù)中的節(jié)點(diǎn),均有一個(gè)對(duì)應(yīng)的指令函數(shù)負(fù)責(zé)該指令的解釋運(yùn)行. 整個(gè)過(guò)程的執(zhí)行是從指令樹(shù)的根節(jié)點(diǎn)開(kāi)始遍歷,并在運(yùn)行時(shí)決定執(zhí)行的路徑.
3.2.3 數(shù)據(jù)庫(kù)訪問(wèn)
在PostgreSQL中,對(duì)于需要訪問(wèn)數(shù)據(jù)庫(kù)的指令,指令函數(shù)需要訪問(wèn)系統(tǒng)提供的內(nèi)部訪問(wèn)接口來(lái)完成數(shù)據(jù)庫(kù)訪問(wèn)操作,例如,執(zhí)行一條查詢語(yǔ)句、插入數(shù)據(jù)或者計(jì)算某個(gè)表達(dá)式的結(jié)果. 在PostgreSQL內(nèi)部,提供了服務(wù)端編程接口(Server Programming Interface)來(lái)接收來(lái)自系統(tǒng)內(nèi)部的數(shù)據(jù)操作和訪問(wèn)請(qǐng)求. 在下表中,表2列舉了接口包含的部分重要訪問(wèn)函數(shù):
表2 SPI訪問(wèn)接口
3.3 存儲(chǔ)策略
存儲(chǔ)過(guò)程的存儲(chǔ),即是對(duì)過(guò)程的持久化,要保證過(guò)程在系統(tǒng)重啟后依舊能夠使用. 在PostgreSQL中,過(guò)程存儲(chǔ)分為兩個(gè)部分,過(guò)程的源代碼和編譯后的指令集.
過(guò)程的源代碼會(huì)被存儲(chǔ)在系統(tǒng)表pg_proc中. 該系統(tǒng)表用于存儲(chǔ)關(guān)于函數(shù)或者過(guò)程的信息. 表3列舉了需要存儲(chǔ)的部分重要屬性. 從表3可以看出,系統(tǒng)表主要存儲(chǔ)了過(guò)程的原始信息,包括名字,源碼,參數(shù),調(diào)用方式等. 由于這些信息是存儲(chǔ)在系統(tǒng)表中的,從而保證了過(guò)程存儲(chǔ)的持久化.
過(guò)程會(huì)在創(chuàng)建或第一次調(diào)用的時(shí)候被編譯. 編譯后的執(zhí)行計(jì)劃會(huì)被存儲(chǔ)在位于系統(tǒng)緩存中的hash表中. 每個(gè)過(guò)程對(duì)應(yīng)一條記錄,主鍵為根據(jù)過(guò)程名和參數(shù)計(jì)算得到的hash值,映射的對(duì)象是過(guò)程編譯后的指令集.
對(duì)過(guò)程的調(diào)用,首先會(huì)從系統(tǒng)表中找到對(duì)應(yīng)的過(guò)程記錄,然后在系統(tǒng)緩存中尋找是否存在已編譯的指令集,如果沒(méi)有,那么根據(jù)系統(tǒng)表中的prosrc字段重新編譯,并存儲(chǔ)到系統(tǒng)緩存中.
表3 pg_proc表的部分重要屬性
3.3 靜態(tài)語(yǔ)言實(shí)現(xiàn)存儲(chǔ)過(guò)程小結(jié)
PostgreSQL采用的存儲(chǔ)過(guò)程實(shí)現(xiàn)方式是定制了自己的過(guò)程語(yǔ)言PL/pgSQL,在系統(tǒng)內(nèi)部實(shí)現(xiàn)過(guò)程的編譯、運(yùn)行和存儲(chǔ). 這種策略的主要工作事實(shí)上可以分為兩個(gè)部分:(1)提供過(guò)程語(yǔ)言的編譯和運(yùn)行機(jī)制;(2)設(shè)計(jì)過(guò)程語(yǔ)言訪問(wèn)數(shù)據(jù)庫(kù)系統(tǒng)的接口. 而大量的工作在于提供過(guò)程語(yǔ)言的基本功能. 在下一節(jié)中,我們將介紹一種更為簡(jiǎn)明的,利用動(dòng)態(tài)語(yǔ)言中的反射機(jī)制來(lái)實(shí)現(xiàn)存儲(chǔ)過(guò)程的策略.
VoltDB[8,9]是一個(gè)內(nèi)存數(shù)據(jù)庫(kù)[10],提供了 NoSQL 數(shù)據(jù)庫(kù)的可伸縮性和傳統(tǒng)關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)的 ACID 一致性. 使用 Java編寫的存儲(chǔ)過(guò)程進(jìn)行數(shù)據(jù)操作. 我們以VoltDB系統(tǒng)為例來(lái)闡述使用動(dòng)態(tài)語(yǔ)言來(lái)實(shí)現(xiàn)存儲(chǔ)過(guò)程的原理,VoltDB在上層采用的是Java語(yǔ)言開(kāi)發(fā),負(fù)責(zé)系統(tǒng)的集群管理,事務(wù)調(diào)度,SQL解析,接受客戶端連接等功能;下層采用的C++語(yǔ)言開(kāi)發(fā),負(fù)責(zé)SQL的執(zhí)行和數(shù)據(jù)的存取. 存儲(chǔ)過(guò)程實(shí)現(xiàn)在Java層.
4.1 反射機(jī)制
從效果來(lái)看,存儲(chǔ)過(guò)程是一段動(dòng)態(tài)添加到系統(tǒng)中執(zhí)行的代碼. 而Java提供的反射機(jī)制具備這樣的功能. 反射機(jī)制允許Java在運(yùn)行時(shí)加載、探知、使用編譯期間完全未知的類. Java可以加載一個(gè)運(yùn)行時(shí)才得知名稱的類,獲悉其完整構(gòu)造,并生成對(duì)象實(shí)體、或修改成員變量,或調(diào)用方法.
VoltDB利用該機(jī)制,為應(yīng)用程序提供了可擴(kuò)展的編程接口,應(yīng)用程序員通過(guò)繼承系統(tǒng)提供的過(guò)程基類,定制應(yīng)用訪問(wèn)數(shù)據(jù)庫(kù)的邏輯,并在系統(tǒng)外部使用Java編譯器進(jìn)行編譯,并最終交由系統(tǒng)在運(yùn)行時(shí)加載和調(diào)用.
4.2 過(guò)程的編譯與執(zhí)行
VoltDB中存儲(chǔ)過(guò)程的流程控制,變量定義等功能完全由Java提供,而對(duì)于需要訪問(wèn)數(shù)據(jù)庫(kù)的操作,設(shè)計(jì)了相應(yīng)的接口. 每一個(gè)存儲(chǔ)過(guò)程都需要繼承這個(gè)接口,并使用相應(yīng)的接口方法來(lái)提交查詢,獲得結(jié)果.
下面我們給出一個(gè)VoltDB中存儲(chǔ)過(guò)程的樣例.
public class KvUdpate extends VoltProcedure {
public final SQLStmtaddRecord = new SQLStmt
“insert into kvstore values(?,?);”
);
publicVoltTable[] run(int a, int b) throws Exception{
voltQueueSQL(addRecord, a, b);
voltExecuteSQL(true);
return null;
}
}
這段代碼會(huì)被Java編譯器編譯. 例如:
$javac -cp $“CLASSPATH:/opt/voltdb/voltdb/*”UpdatePeople.java
這里Java編譯器只負(fù)責(zé)編譯Java語(yǔ)法相關(guān)的內(nèi)容. 而對(duì)于SQL語(yǔ)句是由VoltDB的SQL解析引擎在過(guò)程調(diào)用時(shí)負(fù)責(zé)編譯和緩存.
VoltProcedure主要提供了如表4所示的接口函數(shù):
表4 VoltProcedure主要接口函數(shù)
需要注意的是,在VoltProcedure中并沒(méi)有提供游標(biāo)等功能,對(duì)表的迭代訪問(wèn)是使用VoltTable提供的相應(yīng)操作方法. 編譯與執(zhí)行的整體框架如圖2所示.
圖2 VoltDB存儲(chǔ)過(guò)程框架
4.3 過(guò)程的存儲(chǔ)
存儲(chǔ)過(guò)程采用Java編寫,在系統(tǒng)外進(jìn)行編譯和存儲(chǔ),關(guān)于過(guò)程的信息,例如過(guò)程名,參數(shù),使用的SQL語(yǔ)句會(huì)被存儲(chǔ)在系統(tǒng)表中. 在外部的類文件被調(diào)用后,系統(tǒng)內(nèi)部會(huì)緩存類信息,已備下一次的調(diào)用.
編譯后的存儲(chǔ)過(guò)程會(huì)在系統(tǒng)表中進(jìn)行注冊(cè). 例如:
CREATE PROCEDURE FROM CLASS UpdatePeople;
PARTITION PROCEDURE UpdatePeople ON TABLE people COLUMN state_num;
運(yùn)行以上的命令后,系統(tǒng)表中就記錄了這個(gè)過(guò)程的相關(guān)信息,從而方便之后的再調(diào)用. VoltDB對(duì)過(guò)程的調(diào)用是通過(guò)系統(tǒng)表中提供的過(guò)程名和相應(yīng)的存儲(chǔ)路徑,并據(jù)此,從外部加載相應(yīng)的類,通過(guò)反射機(jī)制生成該類的實(shí)例,調(diào)用相應(yīng)的run()方法.
4.4 支持反射的動(dòng)態(tài)語(yǔ)言實(shí)現(xiàn)存儲(chǔ)過(guò)程小結(jié)
利用動(dòng)態(tài)語(yǔ)言(例如,Java)的反射機(jī)制能夠非常便捷地實(shí)現(xiàn)存儲(chǔ)過(guò)程. 它不需要實(shí)現(xiàn)存儲(chǔ)過(guò)程的編譯和執(zhí)行,從而大大降低了開(kāi)發(fā)難度. 然而,這取決于底層數(shù)據(jù)庫(kù)采用的實(shí)現(xiàn)語(yǔ)言是否能夠提供反射機(jī)制,因而已有利用的動(dòng)態(tài)語(yǔ)言來(lái)實(shí)現(xiàn)存儲(chǔ)過(guò)程有一定的局限性.
OceanBase[3,4]是阿里集團(tuán)研發(fā)的可擴(kuò)展的關(guān)系數(shù)據(jù)庫(kù). 目前的版本暫時(shí)不支持存儲(chǔ)過(guò)程的編寫,本小節(jié)我們將探討在OceanBase中添加存儲(chǔ)過(guò)程模塊的關(guān)鍵技術(shù). OceanBase整機(jī)架構(gòu)分為四個(gè)模塊:主控服務(wù)器RootServer,更新服務(wù)器UpdateServer,基線數(shù)據(jù)服務(wù)器ChunkServer以及合并服務(wù)器MergeServer.
◆ 客戶端:用戶使用OB的方式和MySQL數(shù)據(jù)庫(kù)完全相同,支持JDBC、C客戶端訪問(wèn).
◆ RootServer: 管理集群中的所有服務(wù)器,Tablet數(shù)據(jù)分布以及副本管理. RootServer一般為一主一備,主備之間數(shù)據(jù)強(qiáng)同步.
◆ UpdateServer:存儲(chǔ)OB系統(tǒng)的增量更新數(shù)據(jù). UpdateServer一般為一主一備,UpdateServer和RootServer一般部署在同一服務(wù)器中.
◆ ChunkServer:存儲(chǔ)OB系統(tǒng)的基線數(shù)據(jù). 基準(zhǔn)數(shù)據(jù)一般存儲(chǔ)兩份或者三份.
◆ MergeServer:接收并解析用戶的SQL請(qǐng)求,經(jīng)過(guò)詞法分析、語(yǔ)法分析、查詢優(yōu)化等一系列操作后轉(zhuǎn)發(fā)給相應(yīng)的ChunkServer. 如果請(qǐng)求數(shù)據(jù)分布在多臺(tái)ChunkServer上,MergeServer還需對(duì)多臺(tái)ChunkServer返回的結(jié)果進(jìn)行合并.
圖3 OceanBase架構(gòu)
正如之前所討論的,存儲(chǔ)過(guò)程的模塊的編寫主要有以PostgreSQL為代表的靜態(tài)語(yǔ)言過(guò)程語(yǔ)言的方式,以及以VoltDB為代表的基于動(dòng)態(tài)語(yǔ)言反射機(jī)制的方式.
由于OceanBase是采用C++語(yǔ)言開(kāi)發(fā)的,所以實(shí)現(xiàn)存儲(chǔ)過(guò)程的策略主要參考Postgre的實(shí)現(xiàn)方案. MergerServer是OB查詢的入口,因而存儲(chǔ)過(guò)程實(shí)現(xiàn)在MergeServer上. 添加存儲(chǔ)過(guò)程模塊,需要增加存儲(chǔ)過(guò)程編譯模塊,存儲(chǔ)過(guò)程執(zhí)行模塊,Obsql對(duì)存儲(chǔ)過(guò)程定義和調(diào)用的支持以及存儲(chǔ)過(guò)程訪問(wèn)SQL引擎的接口,其基本框架如圖4所示:
圖4 存儲(chǔ)過(guò)程基本框架設(shè)計(jì)
? 定義存儲(chǔ)過(guò)程的指令首先被MergerServer接收,MergerServer調(diào)用SQL詞法語(yǔ)法解析模塊,對(duì)過(guò)程定義的部分會(huì)轉(zhuǎn)到存儲(chǔ)過(guò)程的編譯模塊完成.
? 編譯后的結(jié)果可以緩存在系統(tǒng)內(nèi)部,過(guò)程源代碼可以存儲(chǔ)在系統(tǒng)表中.
? 調(diào)用一個(gè)存儲(chǔ)過(guò)程通過(guò)SQL查詢的入口點(diǎn),轉(zhuǎn)到存儲(chǔ)過(guò)程的執(zhí)行模塊,當(dāng)遇到需要訪問(wèn)數(shù)據(jù)庫(kù)的操作時(shí),通過(guò)訪問(wèn)接口調(diào)用SQL引擎的編譯或者執(zhí)行模塊.
在MergeServer上,需要封裝存儲(chǔ)過(guò)程訪問(wèn)SQL引擎的訪問(wèn)接口. 該接口需要提供如表5所示的基本訪問(wèn)功能.
表5 OceanBase的服務(wù)端編程接口需要的基本功能
存儲(chǔ)過(guò)程是用戶使用數(shù)據(jù)庫(kù)的重要功能. 它大大改善了前臺(tái)應(yīng)用訪問(wèn)數(shù)據(jù)庫(kù)的性能. 現(xiàn)有的主流數(shù)據(jù)庫(kù)都支持這項(xiàng)功能. 存儲(chǔ)過(guò)程的實(shí)現(xiàn)方案主要取決于數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)和實(shí)現(xiàn)方案. 本文主要闡述了基于靜態(tài)語(yǔ)言和基于動(dòng)態(tài)語(yǔ)言的反射機(jī)制實(shí)現(xiàn)存儲(chǔ)過(guò)程的基本原理. 并進(jìn)一步以采用C開(kāi)發(fā)的PostgreSQL和以Java開(kāi)發(fā)的VoltDB為例,主要討論了存儲(chǔ)過(guò)程在不同系統(tǒng)中的實(shí)現(xiàn)原理. 最后,對(duì)OceanBase中實(shí)現(xiàn)存儲(chǔ)過(guò)程這項(xiàng)功能給出了初步的設(shè)計(jì).
[1] Stored Procedure[EB/OL]. http://en.wikipedia.org/wiki/Stored_procedure.
[2] PL/pgSQL[EB/OL]. http://www.postgresql.org/docs/8.3/static/plpgsql.html.
[3] OceanBase[EB/OL]. http://alibaba.github.io/oceanbase/.
[4] 楊傳輝. 大規(guī)模分布式存儲(chǔ)系統(tǒng)原理解析與架構(gòu)實(shí)戰(zhàn)[M]. 北京:工業(yè)出版社,2013.
[5] 彭智勇,彭煜瑋. PostgreSQL數(shù)據(jù)庫(kù)內(nèi)核分析[M]. 北京:機(jī)械工業(yè)出版社華章公司,2012.
[6] AHO A V, ULLMAN J D. Principles of Compiler Design[M]. [s.L.]:Addison-Wesley, 1977.
[7] STONEBRAKER M, KEMNITZ G. The Postgres Next Generation Database Management System[J]. Commun ACM, 1991, 34(10): 78-92.
[8] KALLMAN R, KIMURA H, NATKINS J, et al. Abadi: H-store: a high-performance, distributed main memory transaction processing system[J]. PVLDB, 2008,1(2): 1496-1499.
[9] STONEBRAKER M, WEISBERG A. The VoltDB Main Memory DBMS[J]. IEEE Data Eng Bull, 2013, 36(2): 21-27.
[10] STONNEBRAKER M, MADDEN S, ABADI D J, et al. The end of an Architectural Era: (It’s Time for a Complete Rewrite)[C]//VLDB ’07: Proceedings of the 33rd International Conference on Very Large Data Bases, 2007: 1150-1160.
(責(zé)任編輯 王善平)
Study on stored procedure implementation oriented to OceanBase
ZHU Tao, ZHOU Min-qi, ZHANG Zhao
(SoftwareEngineerInstitute,EastChinaNormalUniversity,Shanghai200062,China)
A stored procedure is a pre-compiled subroutine stored in database server, which improves the efficiency of applications’ database access. This paper discussed the implementation of stored procedure based on both static language and dynamic language. Besides, we gave a primary design for implementing stored procedures in OceanBase.
stored procedure; database system; SQL
1000-5641(2014)05-0281-09
2014-07
國(guó)家自然科學(xué)基金(12345678);上海市重點(diǎn)學(xué)科建設(shè)項(xiàng)目(98776654)
朱濤,男,博士研究生,研究方向?yàn)閮?nèi)存數(shù)據(jù)庫(kù),分布式系統(tǒng). E-mail:zhutaojs@gmail.com.
周敏奇,男,副教授,碩士生導(dǎo)師,研究方向?yàn)閮?nèi)存數(shù)據(jù)庫(kù). E-mail:mqzhou@sei.ecnu.edu.cn.
TP392
A
10.3969/j.issn.1000-5641.2014.05.025
華東師范大學(xué)學(xué)報(bào)(自然科學(xué)版)2014年5期