一、前言
經常在oracle中寫plsql的應該都知道,oracle中使用package不僅僅是把多個函數或者過程整合到一起,它還具有一些其他的優點,比如
- 可以定義私有化函數、過程、type、type數組、變量等,讓其只能被包內的函數調用
- 可以在不同的package內使用同名的函數、過程、type、type數組、變量等
- 在調試代碼時,不需要跳轉很多對象
- 把package的ddl文本中的包名進行修改,即可創建一個內部代碼完全相同但名字不同的包,無需去修改內部其他函數或過程的名稱,方便進行測試
實際優點遠遠不止上面提到的這些。
二、PostgreSQL的"package"
PostgreSQL是不支持創建package的,更別提調用package中的過程及函數了。
目前信創環境下,有大量的oracle數據庫被要求遷移到開源或國產數據庫,但其中的package遷移變成了一個很讓人頭疼的事,要么重新開發對應的plsql代碼,要么就要讓目標數據庫支持使用package。
常見的在oracle中調用package中過程的語法為
begin
pck.pro;
end;
上例中,pck為包名,pro為過程名,或者也可以被識別為schema_name.procedure_name,oracle會自動識別前面的是包名還是schema_name。但是這個語法如果放到PostgreSQL中,就只能是后者了,即pck這個schema下的pro過程,這個時候過程就不在package里了。
因此,有人想到了一種方法,直接用包名建一個schema,然后在這個schema下來創建原有包內的過程及函數。至于會話內的參數暫存及傳遞,使用會話級臨時表進行處理。
這樣就可以變相地支持package的調用語法了。
但是,這個方法的缺點也顯而易見
- 創建一個"package"就得創建一個schema,大型業務系統遷入進來恐怕得有幾十上百個schema了,難以管理
- 這些過程及函數相當于是全部獨立的,無論是在解析還是在參數的處理上,性能都不如單獨的一個package
- 創建一個"package"也變得困難,需要逐個過程或逐個函數去創建
三、openGauss的"package"
在2021年9月份,openGauss發布了2.1.0版本(Preview),并在2021年12月份宣布支持"create or replace package"。
眾所周知,openGauss是基于PostgreSQL開源項目進行的研發,那它是如何實現支持package的呢?
首先,新建openGauss數據庫時,必須要指定兼容A(參考【openGauss】構建一個兼容Oracle模式支持創建package的openGauss的docker鏡像),然后在數據庫里就可以按照類似oracle語法那樣來創建包了,比如
CREATE OR REPLACE package dbms_lock
as
PROCEDURE sleep(pi_seconds double precision);
end dbms_lock;
/
CREATE OR REPLACE package body dbms_lock
as
PROCEDURE sleep(pi_seconds double precision)
AS
BEGIN
perform pg_sleep(pi_seconds);
END;
end dbms_lock;
/
還可以像在oracle中一樣調用
begin
dbms_lock.sleep(1);
end;
不過要注意的是,在創建包說明和包體的最后,必須要接上包名(ORACLE可接可不接,但是如果接了必須保持和開頭的包名一致);還有PostgreSQL(openGauss)在不指定schema時創建的過程及函數,是默認創建在pg_catalog這個schema下的,同理package也是如此。
到這里,我突然產生了一個疑問,在pg_proc能不能找到這個包呢?
select * from pg_proc x where LOWER(x.proname) like '%dbms_lock%';
結果是沒有記錄???
然后我去查看所有schema,的確也沒有"dbms_lock"這個schema,那這個對象跑哪去了?
再查
select * from pg_proc x where LOWER(x.proname) like '%sleep%';

通過drop再create,確認第二個行記錄的確就是通過創建包來生成的一個過程,然后可以發現它的propackage和propackageid這兩個字段和其他過程是有區別的。我猜測propackageid表示這個過程對應的包id,如果一個包里有多個過程,那么它們的propackageid應該是一樣的,實測也證實的確如此。

可是包名到哪去了?直覺告訴我,應該還有個視圖或者表有記錄包的信息,于是查
select * from pg_class where relname like '%package%'

再查第一個表

果然id是對應的,都是16387
但問題又來了,在PostgreSQL中,pg_proc里的過程及函數,對于一個schema下同名且同參數的,是禁止同時存在多個的,具有唯一性,openGauss這個實現方式難道越過了這個限制么?
答案是,沒有。
我翻了openGauss的官方文檔
https://opengauss.org/zh/docs/2.1.0/docs/Developerguide/CREATE-PACKAGE.html

- 由于升級的限制,在V5R2C00版本中,即使在不同的Package間,也無法創建同名同參的存儲過程。
- package只支持集中式,無法在分布式中使用。
- 在package specification中聲明過的函數或者存儲過程,必須在package body中找到定義。
- 在實例化中,無法調用帶有commit/rollback的存儲過程。
- 不能在Trigger中調用package函數。
- 不能在外部SQL中直接使用package當中的變量。
- 不允許在package外部調用package的私有變量和存儲過程。
- 不支持其它存儲過程不支持的用法,例如,在function中不允許調用commit/rollback,則package的function中同樣無法調用commit/rollback。
- 不支持schema與package同名。
- 只支持A風格的存儲過程和函數定義。
- 不支持package內有同名變量,包括包內同名參數。
- package的全局變量為session級,不同session之間package的變量不共享。
- package中調用自治事務的函數,不允許使用公有變量,以及遞歸的使用公有變量的函數。
- package中不支持聲明ref cursor類型。
看到這一堆限制,尤其是1、5、10,這真是太可惜了。對于package來說,該有的限制是加上了,但該支持的功能卻有一些不支持。
比如說第1點,以前寫個業務功能代碼,建個包,里面經常會寫個名字為"main"的過程,方便開發人員找到入口;如果是導出的話,對應的過程也會命名為"export_data"之類的。這些都屬于企業內部約定俗成的開發規范。但openGauss有這個限制的話,就無法對原有系統實現無縫遷移了,還是得改應用代碼。
第5點這個限制有點莫名其妙,有可能是pg內核的原因。
第6點這個有點不理解,實測在package外部是可以用package的變量的。
CREATE OR REPLACE package dbms_lock
as
pi_seconds double precision DEFAULT 1;
PROCEDURE sleep(pi_seconds double precision);
end dbms_lock;
/
CREATE OR REPLACE package body dbms_lock
as
PROCEDURE sleep(pi_seconds double precision)
AS
BEGIN
perform pg_sleep(pi_seconds);
END;
end dbms_lock;
/
declare
a double precision;
begin
set client_min_messages='notice';
a:=dbms_lock.pi_seconds;
raise notice '%',a;
end;
第11點也不理解,實測可以在包內使用同名變量和同名參數。
CREATE OR REPLACE package dbms_lock
as
pi_seconds double precision DEFAULT 1;
PROCEDURE sleep(pi_seconds double precision);
PROCEDURE sleep2(pi_seconds double precision);
end dbms_lock;
/
CREATE OR REPLACE package body dbms_lock
as
PROCEDURE sleep(pi_seconds double precision)
AS
a int;
BEGIN
perform pg_sleep(pi_seconds);
END;
PROCEDURE sleep2(pi_seconds double precision)
AS
a int;
BEGIN
perform pg_sleep(pi_seconds);
END;
end dbms_lock;
/
另外,這種方式創建的包,無法和目前官方的compat_tools兼容,因為compat_tools是用新建一個包就建一個schema的方式寫的,而這也違背了第9點。
四、總結
整體來看,增加package的支持給openGauss帶來了不小的優勢,可以讓熟悉package開發的人員迅速上手來進行業務系統邏輯代碼的研發。但是受限于pg的內核,目前看上去openGauss只是在sql解析階段來兼容了package的創建及使用語法,在后臺仍舊是轉換成一個個單獨的過程來執行,并沒有創造一種新的對象,由此帶來了官方文檔中那一系列限制,這是在未來開發使用中需要注意的。
- 本文作者: DarkAthena
- 本文鏈接: https://www.darkathena.top/archives/about-opengauss-package
- 版權聲明: 本博客所有文章除特別聲明外,均采用CC BY-NC-SA 3.0 許可協議。轉載請注明出處!




