Oracle的存儲過程,是我們使用數據庫應用開發的重要工具手段。在存儲過程中,我們大部分應用場景都是使用DML語句進行數據增刪改操作。本篇中,我們一起探討一下數據定義語句DDL在存儲過程中使用的細節和要點。
1、“借道而行”的DDL
從Oracle PL/SQL和存儲過程程序開發原則上,應該是不鼓勵在SP中使用DDL語句的。首先一個表現,就是Oracle在編譯時就不允許直接在SP中使用DDL語句。下面我們使用Oracle 10gR2作為實驗環境。
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle Database10gEnterpriseEdition Release10.2.0.1.0 - Prod
PL/SQL Release10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for 32-bit Windows: Version10.2.0.1.0 - Production
NLSRTL Version10.2.0.1.0 – Production
建立存儲過程p_test_nc,進行簡單的數據表創建。
SQL> create or replace procedure P_TEST_NC
2 is
3 begin
4 create table t (id number);
5 end P_TEST_NC;
6 /
Warning: Procedure created with compilation errors
SQL> select name, text from user_errors;
NAME TEXT
---------- --------------------------------------------------------------------------------
P_TEST_NC PLS-00103:出現符號"CREATE"在需要下列之一時:
begin case declare exit
for goto if loop mod null pragma raise return select update
while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe
顯然,在編譯時Oracle就報錯不允許存儲過程創建。之后的實驗drop和truncate table操作,也都是不允許直接在存儲過程中書寫DDL語句。說明起碼使用直接的DDL語句,存儲過程是不能編譯通過的。
那么,有沒有什么折中的方法呢?我們說是有的,就是借助“execute immediate”方法,“繞過”編譯過程中對DDL的屏蔽。我們使用truncate table DDL語句實驗。
SQL> create or replace procedure P_TEST_NC
2 is
3 begin
4 execute immediate'truncate table t';
5 end P_TEST_NC;
6 /
Procedure created
編譯通過了,DDL語句以一個字符串的形式避開了編譯時Oracle的語法檢查,編譯成功。那么,執行起來會不會報運行時錯誤呢?
SQL> exec p_test_nc;
PL/SQL procedure successfully completed
執行成功,說明:在Oracle存儲過程中,可以使用exectue immediate語句繞開編譯時對DDL語句的檢查,生成運行代碼。
2、SP中DDL權限
任何程序編譯執行都會伴隨着語法語義的一系列檢查。使用execute immediate雖然可以回避編譯時檢查,但是SQL語句還是面臨着運行時檢查的問題。下面看實驗的例子。
--在scott用戶下進行試驗;
SQL> create or replace procedure P_TEST_NC
2 is
3 begin
4 execute immediate 'create table t(id number)';
5 end P_TEST_NC;
6 /
Procedure created–編譯時通過;
SQL> exec p_test_nc;
begin p_test_nc; end;
ORA-01031:權限不足
ORA-06512:在"SCOTT.P_TEST_NC", line 4
ORA-06512:在line 1
在用戶自己的schema下創建數據表,難道是不允許的嗎?顯然不是。
SQL> create table m (id number);
Table created
單獨創建是允許的,說明是由於權限機制導致的問題。我們切換到sys用戶上,提高scott用戶權限。
Connected as SYS
--賦予最高創建數據表的系統權限;
SQL> grant create any table to scott;
Grant succeeded
切換回scott用戶,繼續實驗。
SQL> conn scott/tiger@orcl;
Connected to Oracle Database10gEnterpriseEdition Release10.2.0.1.0
Connected as scott
SQL> exec p_test_nc;
PL/SQL procedure successfully completed
SQL> select * from t;
ID
----------
執行成功!這個原因是什么呢?還是由於存儲過程權限體系特點和DDL語句特點共同造成的。
在之前筆者的系列文章《所有者權限和調用者權限》(http://space.itpub.net/17203031/viewspace-692161)中,介紹了Oracle存儲過程采用的兩種權限體系方式和role權限在存儲過程執行中的特殊性。
默認情況下,Oracle對存儲過程是使用所有者權限,也就是說:如果用戶B調用了用戶A schema下的一個存儲過程,其中使用的對象權限和系統權限,全部都是用戶A的。如果用戶A沒有權限,用戶B執行要報錯。
同時,用戶的角色權限在進入存儲過程后,會被剝離掉,是不起效果的。
結合上面的實驗,就好解釋了:scott自身只擁有一個resource的角色權限,單獨在SQL中使用沒有問題。進入到SP之后,這個create table的權限就被剝離掉了。而該SP存在被其他用戶調用生成數據表的可能。所以會在運行時報錯權限不足。
當我們顯示的賦予scott用戶create any table/create table之后,系統權限就可以滲透到SP中起效果了。
這並不是解決該問題的唯一方法。此處我們可以使用調用者權限機制,改寫SP代碼。首先我們剔除掉scott的create any table權限。
Connected to Oracle Database10gEnterpriseEdition Release10.2.0.1.0
Connected as SYS
SQL> revoke create any table from scott;
Revoke succeeded
SQL> conn scott/tiger@orcl;
Connected to Oracle Database10gEnterpriseEdition Release10.2.0.1.0
Connected as scott
SQL> exec p_test_nc;
begin p_test_nc; end;
ORA-01031:權限不足
ORA-06512:在"SCOTT.P_TEST_NC", line 4
ORA-06512:在line 1
我們改寫代碼為:
SQL> create or replace procedure P_TEST_NC
2 Authid Current_User
3 is
4 begin
5 execute immediate 'create table t (id number)';
6 end P_TEST_NC;
7 /
Procedure created
SQL> exec p_test_nc;
PL/SQL procedure successfully completed
執行成功,這里使用“authid Current_user”將存儲過程轉化為調用者權限。每次調用存儲過程,都是動態根據調用者的權限構成去判定是否有權限,這樣就回避了該問題的出現。
總之:在使用DDL在存儲過程中時,權限管理和使用的復雜度是在增加。
4、DDL對事務的提交影響
將DDL語句放置在存儲過程中,潛在最大風險就是對事務管理的破壞。在Oracle中,如果調用一個DDL語句,潛藏效果就是將當前會話的未提交事務進行提交。這個過程顯然是對原有的事務邏輯破壞。
SQL> create table m (id number);
Table created
SQL> select * from m;
ID
----------
SQL> create or replace procedure P_TEST_NC
2 is
3 begin
4 insert into m values (3);
5 execute immediate 'truncate table t';
6
7 rollback;
8 end P_TEST_NC;
9 /
Procedure created
--執行代碼
SQL> exec p_test_nc;
PL/SQL procedure successfully completed
--事務提交
SQL> select * from m;
ID
----------
3
從上面的例子上,我們可以清楚的看到現象。由於中間的truncate table操作,引起數據表m的插入操作被提交commit。而真正的事務邏輯可能是一個rollback。
所以,在SP中使用DDL命令,可能引起業務邏輯的不可控提交和數據不一致,這個風險在任何應用中是不可以允許的。
那么,有沒有方法回避這個過程呢?經一個同事提醒,的確可以使用手段回避。
5、DDL與自治事務
自治事務(AUTONOMOUS_TRANSACTION)是保證在事務進行過程中一段獨立的事務過程。如果在DDL操作外套入一個自治事務過程,是否就可以回避問題了。
SQL> select * from m;
ID
----------
SQL> create or replace procedure P_TEST_NC is
2 procedure p_inner_test
3 is
4 PRAGMA AUTONOMOUS_TRANSACTION;
5 begin
6 --調用ddl
7 execute immediate 'truncate table t';
8 end;
9 begin
10 insert into m values (3);
11 p_inner_test;
12
13 rollback;
14 end P_TEST_NC;
15 /
Procedure created
SQL> exec p_test_nc;
PL/SQL procedure successfully completed
\
SQL> select * from m;
ID
----------
實驗成功,通過自治事務的確可以回避DDL的事務問題。
6、結論
DDL在SP中,與常規的DML操作差異很大。這種差異不僅僅是語法上,更多的是權限、事務等更深層次復雜的差異。所以,從Oracle的角度看,盡量少在SP中使用DDL語句,避免出現不可控的問題。
PLS-00157: AUTHID only allowed on schema-level programs
查了下錯誤原因 An AUTHID clause was specified for a subprogram inside a package or type. These clauses are only supported for top-level stored procedures, packages, and types.
大致意思就是authid只能用在頂級的存儲過程、包、類型上,不能用在包或類型的子程序上。
在包上加入authid,執行正常了。
create or replace package rule_execute
authid current_user