存儲過程(PROCEDURE)是由一組預編譯的SQL語句組成的集合,可以在數據庫中進行存儲并反復調用。本文為您介紹在Hologres中使用存儲過程的方法。
使用限制
Hologres從V3.0版本起支持PL/pgSQL語法的存儲過程,PL/pgSQL語法詳情請參見SQL Procedural Language。
在Hologres的存儲過程中,支持多條DDL語句事務,支持多條DML混合事務,暫不支持DDL和DML混合事務,詳情請參見SQL事務能力。
存儲過程不支持設置返回值,無法作為UDF(User-defined Functions)使用。
權限說明
CREATE PROCEDURE需要用戶有Database中的Create權限,與新建表權限一致,詳情請參見SQL-CREATE PROCEDURE。
CREATE OR REPLACE需要用戶同時擁有Database的Create權限和目標存儲過程的OWNER權限,詳見SQL-CREATE PROCEDURE。
執行存儲過程需要用戶有存儲過程的EXECUTE權限,詳見SQL-CALL。
命令參考
Hologres支持的存儲過程語法兼容PostgreSQL,具體語法如下:
創建存儲過程
CREATE [ OR REPLACE ] PROCEDURE
<procedure_name> ([<argname> <argtype>])
LANGUAGE 'plpgsql'
AS <definition>;
參數 | 說明 |
procedure_name | 存儲過程名稱。 |
argname | 參數名稱。參數可選,取決于存儲過程設計。 |
argtype | 參數類型。 |
definition | 定義存儲過程的具體實現,可以是一個SQL語句或者代碼塊。 |
更多參數詳情請參見SQL-CREATE PROCEDURE。
修改存儲過程
ALTER PROCEDURE <procedure_name> ([<argname> <argtype>])
OWNER TO <new_owner> | CURRENT_USER | SESSION_USER;
參數 | 說明 |
new_owner | 新用戶名。 |
CURRENT_USER | 當前用戶。 |
SESSION_USER | 會話用戶。 |
參數詳情請參見SQL-ALTER PROCEDURE。
刪除存儲過程
DROP PROCEDURE [ IF EXISTS ] <procedure_name> ([<argname> <argtype>]);
參數詳情請參見SQL-DROP PROCEDURE。
執行存儲過程
CALL <procedure_name> ([<argument>]);
參數 | 說明 |
argument | 存儲過程所需的參數。參數可選,取決于存儲過程設計。 |
參數詳情請參見SQL-CALL。
使用示例
示例1:含多條DDL語句事務的存儲過程。
創建存儲過程。
CREATE OR REPLACE PROCEDURE procedure_1() LANGUAGE 'plpgsql' AS $$ BEGIN --- TXN1 --- CREATE TABLE a1(key int); CREATE TABLE a2(key int); COMMIT; --- TXN2 --- CREATE TABLE a3(key int); CREATE TABLE a4(key int); ROLLBACK; END; $$;
調用存儲過程:表a1、a2創建成功,a3、a4未創建。
CALL procedure_1();
示例2:含多條DML語句事務的存儲過程。
創建存儲過程
CREATE OR REPLACE PROCEDURE procedure_2() LANGUAGE 'plpgsql' AS $$ BEGIN INSERT INTO a1 VALUES(1); INSERT INTO a2 VALUES(2); ROLLBACK; END; $$; CREATE OR REPLACE PROCEDURE procedure_3() LANGUAGE 'plpgsql' AS $$ BEGIN INSERT INTO a1 VALUES(1); INSERT INTO a2 VALUES(2); END; $$;
執行存儲過程
執行procedure_2:支持ROLLBACK,數據未成功寫入
-- 開啟DML事務功能 SET hg_experimental_enable_transaction = ON; -- 執行存儲過程 CALL procedure_2();
執行procedure_3:數據成功寫入
-- 開啟DML事務功能 SET hg_experimental_enable_transaction = ON; -- 執行存儲過程 CALL procedure_3();
示例3:同時含DDL和DML的存儲過程。
創建存儲過程:Hologres暫不支持DDL和DML混合事務,因此在存儲過程中,需要對DDL和DML分別執行COMMIT。
CREATE OR REPLACE PROCEDURE procedure_4() LANGUAGE 'plpgsql' AS $$ BEGIN INSERT INTO a1 VALUES(1); COMMIT; CREATE TABLE bb(key int); COMMIT; INSERT INTO a1 VALUES(2); INSERT INTO bb VALUES(1); COMMIT; END; $$;
執行存儲過程:建表和數據寫入均成功。
-- 開啟DML事務功能 SET hg_experimental_enable_transaction = ON; -- 執行存儲過程 CALL procedure_4();
示例4:含常見用法的存儲過程包括定義入參、定義中間變量、定義循環、定義IF條件、定義EXCEPTION等。
創建存儲過程。
CREATE OR REPLACE PROCEDURE procedure_5(input text) LANGUAGE 'plpgsql' AS $$ -- 定義中間變量 DECLARE sql1 text; BEGIN -- 向入參的表里寫入一行數據 EXECUTE 'insert into ' || input || ' values(1);'; COMMIT; -- 建a3表 CREATE TABLE a3(key int); COMMIT; -- 使用中間變量,向a3表寫入一條數據 sql1 = 'insert into a3 values(1);'; EXECUTE sql1; -- 定義FOR循環 FOR i IN 1..10 LOOP BEGIN -- i=1已存在表中,所以只打一條日志 IF i IN (SELECT KEY FROM a3) THEN RAISE NOTICE 'Data already exists.'; -- 其他數字不存在表中,所以嘗試寫入,同時RAISE EXCEPTION,而后COMMIT ELSE INSERT INTO a3 VALUES(i); RAISE EXCEPTION 'HG_PLPGSQL_NEED_RETRY'; COMMIT; END IF; -- 針對RAISE的EXCEPTION,打一條日志 EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Catch error.'; END; END LOOP; END; $$;
執行存儲過程:a3表中寫入數據1、其余數據不寫入,相關日志全部打印。
-- 開啟DML事務功能 SET hg_experimental_enable_transaction = ON; -- 執行存儲過程 CALL procedure_5('a1');
管理存儲過程
查看已創建的存儲過程。
SELECT p.proname AS function_name, p.prosrc AS function_detail, n.nspname AS schema_name, r.rolname AS owner_name, d.description AS description FROM pg_proc p INNER JOIN pg_namespace n ON p.pronamespace = n.oid INNER JOIN pg_roles r ON p.proowner = r.oid LEFT JOIN pg_description d ON p.oid = d.objoid WHERE r.rolname != 'holo_admin' AND p.prokind = 'p' ORDER BY n.nspname, p.proname;
查看存儲過程定義。
SELECT pg_get_functiondef('<procedure_name>'::regproc);
常見問題
由于Hologres是分布式系統,其中接入節點FE也是分布式的。當表發生DDL變更時,不同接入節點之間需要實時同步元數據,如果元數據未同步完成,DDL變更可能會失敗。針對上述場景,Hologres在大部分情況下會自動重試,無需手動重復提交DDL變更。但在存儲過程中,無法支持自動重試,上述場景會直接返回錯誤信息為“HG_PLPGSQL_NEED_RETRY”的報錯。
針對高頻DDL變更的表,建議在存儲過程中手動定義重試邏輯,以免存儲過程頻繁報錯。重試邏輯如下:
CREATE OR REPLACE PROCEDURE procedure_6()
LANGUAGE 'plpgsql'
AS $$
BEGIN
WHILE TRUE LOOP
BEGIN
-- 嘗試執行DDL語句,如果成功,則退出循環
CREATE TABLE a3(key int);
COMMIT;
EXIT;
EXCEPTION
-- 如果遇到HG_PLPGSQL_NEED_RETRY報錯,則打印日志,并自動重試
WHEN HG_PLPGSQL_NEED_RETRY THEN
RAISE NOTICE 'DDL need retry';
END;
END LOOP;
END;
$$;