oracle 静态SQL和动态SQL

2023年 12月 8日 27.2k 0

PL/SQL提供了不同的方法来执行SQL语句。主要的两个类别是静态SQL和动态SQL。动态SQL能够进一步分成三个子类别:EXECUTEI
MMEDIATE、OPEN/FETCH/CLOSE以及dbms_sql包。唯一与解析相关的功能是它们都可以使用绑定变量。而实际上,只有重用语句和缓存客户端语句的部分可以使用。它们并不是对所有SQL语句类别都有效。

 

注意鉴于PL/SQL是在数据库引擎中运行的,讨论客户端语句缓存好像有点奇怪。其实,从SQL引擎的视角来看,PL/SQL引擎就是一个客户端。在这个客户端中,客户端语句缓存的概念将在这里实现

 

在本节例子中提供的PL/SQL代码块来自ParsingTest1.sql、ParsingTest2.sql以及ParsingTest3.sql脚本,分别实现测试案例1、2和3。

 

1.1.1.1   静态SQL

静态SQL被集成到PL/SQL语言中。就像它的名称一样它是静态的,因此,在PL/SQL编译期间SQL语句必须是完全已知的。出于这个原因,如果一条SQL语句引用了PL/SQL变量,则不可避免地要使用绑定变量。例如,不可能使用静态SQL写出一段代码来重现测试案例1。

 

编写静态SQL有两种方式。第一种是基于隐式游标,但它没有控制游标生命周期的能力。下面的PL/SQL代码块实现了测试案例2:

DECLARE

 
l_pad VARCHAR2(4000);

BEGIN

  FOR
i IN 1..10000

 
LOOP

   
select pad INTO l_pad FROM t WHERE val = i;

  END
LOOP;

END;

/

 

第二种方式是基于显式游标。在这种情况下,可以对游标进行某些控制。不管怎样,打开/解析/执行阶段被合并成一个单独的操作(OPEN)。这意味着仅可以控制提取和关闭阶段。下面的PL/SQL代码块实现了测试案例2:

DECLARE

 
CURSOR c (p_val NUMBER) IS SELECT pad FROM t WHERE val = p_val;

 
l_pad VARCHAR2(4000);

BEGIN

  FOR
i IN 1..10000

 
LOOP

   
OPEN c(i);

   
FETCH c INTO l_pad;

   
CLOSE c;

  END
LOOP;

END;

/

 

尽管这两种方式都防止了不良代码(测试案例1),但它们也不允许写出特别高效的代码(测试案例3)。这是因为没有完全控制游标。但从性能的角度看,这两种方法是类似的。

 

为了解决这个问题,可以使用客户端语句缓存。缓存游标的最大数量由session_cached_cursors初始化参数决定。在10.2版本中,默认的缓存游标数量是20,而从11.1版本开始是50。这个初始化参数,并不与客户端语句缓存直接相关,而是“错误”地配置了它!事实上,这与用于控制服务器端语句缓存的初始化参数是同一个。

1.1.1.2   本地动态SQL:EXECUTE IMMEDIATE

从游标管理的角度来看,基于EXECUTE
IMMEDIATE的本地动态SQL与使用隐式游标的静态SQL类似。换句话说,它不能控制游标的生命周期。下面的PL/SQL代码块实现了测试案例2:

DECLARE

 
l_pad VARCHAR2(4000);

BEGIN

  FOR
i IN 1..10000

 
LOOP

    EXECUTE
IMMEDIATE 'SELECT pad FROM t WHERE val = :1' INTO l_pad USING i;

  END
LOOP;

END;

/

 

没有了对游标的控制,不可能写出实现测试案例3的代码。出于这个原因,可以像静态SQL那样使用客户端游标缓存。

 

1.1.1.3   本地动态SQL:OPEN/FETCH/CLOSE

从游标管理的角度来看,基于OPEN/FETCH/CLOSE的本地动态SQL与使用隐式游标的静态SQL类似。换句话说,它仅能控制提取(FETCH)阶段。下面的PL/SQL代码块实现了测试案例2:

DECLARE

 
TYPE t_cursor IS REF CURSOR;

 
l_cursor t_cursor;

 
l_pad VARCHAR2(4000);

BEGIN

  FOR
i IN 1..10000

 
LOOP

   
OPEN l_cursor FOR 'SELECT pad FROM t WHERE val = :1' USING i;

   
FETCH l_cursor INTO l_pad;

   
CLOSE l_cursor;

  END
LOOP;

END;

/

 

没有对游标的完全控制,不可能写出实现测试案例3的代码。此外,使用基于OPEN/FETCH/CLOSE的动态SQL,数据库引擎无法利用客户端语句缓存。这意味着要解决这种代码引起的解析问题的唯一途径,是使用EXECUTE IMMEDIATE或dbms_sql包对语句进行改写。作为一种变通方案,还可以考虑服务器端语句缓存。

 

1.1.1.4   本地动态SQL:dbms_sql包

dbms_sql包提供对游标的生命周期的完全控制。在下面的PL/SQL代码块中(测试案例2),请注意显式编码的每一步:

DECLARE

 
l_cursor INTEGER;

 
l_pad VARCHAR2(4000);

 
l_retval INTEGER;

BEGIN

  FOR
i IN 1..10000

 
LOOP

   
l_cursor := dbms_sql.open_cursor;

   
dbms_sql.parse(l_cursor, 'SELECT pad FROM t WHERE val = :1', 1);

   
dbms_sql.define_column(l_cursor, 1, l_pad, 10);

   
dbms_sql.bind_variable(l_cursor, ':1', i);

   
l_retval := dbms_sql.execute(l_cursor);

   
IF dbms_sql.fetch_rows(l_cursor) > 0

   
THEN 

     
NULL;

   
END IF;

   
dbms_sql.close_cursor(l_cursor);

  END
LOOP;

END;

/

 

因为可以完全控制游标,实现测试案例3就没有问题了。下面的PL/SQL代码块展示了这样的例子。注意,为了避免不必要的软解析,准备游标(open_cursor、parse、define_column)和关闭游标(close_cursor)的过程被放置到循环外。

DECLARE

 
l_cursor INTEGER;

 
l_pad VARCHAR2(4000);

 
l_retval INTEGER;

BEGIN

 
l_cursor := dbms_sql.open_cursor;

 
dbms_sql.parse(l_cursor, 'SELECT pad FROM t WHERE val = :1', 1);

 
dbms_sql.define_column(l_cursor, 1, l_pad, 10);

  FOR
i IN 1..10000

 
LOOP

   
dbms_sql.bind_variable(l_cursor, ':1', i);

   
l_retval := dbms_sql.execute(l_cursor);

   
IF dbms_sql.fetch_rows(l_cursor) > 0

   
THEN 

     
NULL;

   
END IF;

  END
LOOP;

 
dbms_sql.close_cursor(l_cursor);

END;

/

 

使用dbms_sql包时,数据库引擎无法利用客户端语句缓存。所以,为了优化一个有太多软解析的应用程序(如测试案例2),必须修改它以重用游标(如测试案例3)。作为一个权宜方案,可以考虑服务器端语句缓存。

 

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论