添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Oracle 的存储过程,是我们使用数据库应用开发的重要工具手段。在存储过程中,我们大部分应用场景都是使用 DML 语句进行数据增删改操作。本篇中,我们一起探讨一下数据定义语句 DDL 在存储过程中使用的细节和要点。

1 、“借道而行”的 DDL

Oracle PL/SQL 和存储过程程序开发原则上,应该是不鼓励在 SP 中使用 DDL 语句的。首先一个表现,就是 Oracle 在编译时就不允许直接在 SP 中使用 DDL 语句。下面我们使用 Oracle 10gR2 作为实验环境。

SQL> select * from v$version;

BANNER

----------------------------------------------------------------

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod

PL/SQL Release 10.2.0.1.0 - Production

CORE 10.2.0.1.0 Production

TNS for 32-bit Windows: Version 10.2.0.1.0 - Production

NLSRTL Version 10.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

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 Database 10g Enterprise Edition Release 10.2.0.1.0

Connected as scott

SQL> exec p_test_nc;

PL/SQL procedure successfully completed

SQL> select * from t;

----------

执行成功!这个原因是什么呢?还是由于 存储过程权限体系特点和 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 Database 10g Enterprise Edition Release 10.2.0.1.0

Connected as SYS

SQL> revoke create any table from scott;

Revoke succeeded

SQL> conn scott/tiger@orcl;

Connected to Oracle Database 10g Enterprise Edition Release 10.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;

----------

SQL> create or replace procedure P_TEST_NC

2 is

3 begin

4 insert into m values (3);

5 execute immediate 'truncate table t';

7 rollback;

8 end P_TEST_NC;

9 /

Procedure created

-- 执行代码

SQL> exec p_test_nc;

PL/SQL procedure successfully completed

-- 事务提交

SQL> select * from m;

----------

从上面的例子上,我们可以清楚的看到现象。由于中间的 truncate table 操作,引起数据表 m 的插入操作被提交 commit 。而真正的事务逻辑可能是一个 rollback

所以,在 SP 中使用 DDL 命令, 可能引起业务逻辑的不可控提交和数据不一致,这个风险在任何应用中是不可以允许的。

那么,有没有方法回避这个过程呢?经一个同事提醒,的确可以使用手段回避。

5 DDL 与自治事务

自治事务( AUTONOMOUS_TRANSACTION )是保证在事务进行过程中一段独立的事务过程。如果在 DDL 操作外套入一个自治事务过程,是否就可以回避问题了。

SQL> select * from m;

----------

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;

13 rollback;

14 end P_TEST_NC;

15 /

Procedure created

SQL> exec p_test_nc;

PL/SQL procedure successfully completed

SQL> select * from m;

----------

实验成功,通过自治事务的确可以回避 DDL 的事务问题。

6 、结论

DDL SP 中,与常规的 DML 操作差异很大。这种差异不仅仅是语法上,更多的是权限、事务等更深层次复杂的差异。所以,从 Oracle 的角度看,尽量少在 SP 中使用 DDL 语句,避免出现不可控的问题。

广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员