2

为什么 oracle 总是解析下面的查询?

select MY_SEQUENCE_SEQ.nextval
from dual

来自 Quest SQL Optimizer (8.6.0) 的 SGA 统计数据:

执行:83630

Parse_calls:83630

序列详情:

  • 最后缓存值:1
  • 增量:1
  • 缓存大小:20
  • 周期:没有
  • 订单号

测试场景:

  1. 创建音序器:

    CREATE SEQUENCE MY_SEQUENCE_SEQ
      START WITH 1
      MAXVALUE 999999999999999999999999999
      MINVALUE 1
      NOCYCLE
      CACHE 20
      NOORDER;
    
  2. 对有权访问 v$sql 视图的用户执行此查询。

    select executions, 
           parse_calls 
      from v$sql 
     where sql_text like 'select MY_SEQ%';`
    
  3. 以序列 n 次执行查询

  4. 从第 2 点执行查询。

结果得到:

EXECUTIONS  - n
PARSE_CALSS - n

测试:

数据库:Oracle 数据库 10g 版本 10.2.0.4.0 - 64 位生产

客户端:Toad 版本 11.5.1.2

4

2 回答 2

4

这不是 Oracle 的错误,它只是 TOAD 向 oracle 发送 SQL 的方式。即toad 不会将语句句柄缓存到oracle,它只是在完成时关闭它。

当查询发送到 SQL 引擎时,Oracle 将对查询执行三项主要操作之一。

  1. 硬解析它
  2. 软解析它
  3. 不解析它

即我们想要在案例 3 中,我们当然不想在案例 1 中!那么每个案例什么时候会发生呢?

当 SQL 根本不在共享池中或 SQL 在共享池中但正在使用的绑定变量/文字意味着当前 SQL 不可用时,将发生硬解析。例如,假设我们发布了此 SQL 三次select MY_SEQUENCE_SEQ.nextval from dual。这将在 Oracle 第一次看到此 SQL 并将其放入共享池时进行硬解析,并在第 2 次和第 3 次调用时进行软解析。我们可以很容易地看到这种情况发生:

SQL> select n.name, s.value from v$mystat s, v$statname n where n.statistic# = s.statistic# and n.name in ('parse count (hard)', 'parse count (total)');

NAME                      VALUE
-------------------- ----------
parse count (total)         522
parse count (hard)          287

SQL> select /* test1 */ MY_SEQUENCE_SEQ.nextval
  2  from dual;

   NEXTVAL
----------
        62

SQL> select /* test1 */ MY_SEQUENCE_SEQ.nextval
  2  from dual;

   NEXTVAL
----------
        63

SQL> select /* test1 */ MY_SEQUENCE_SEQ.nextval
  2  from dual;

   NEXTVAL
----------
        64

SQL> select n.name, s.value from v$mystat s, v$statname n where n.statistic# = s.statistic# and n.name in ('parse count (hard)', 'parse count (total)');

NAME                      VALUE
-------------------- ----------
parse count (total)         526
parse count (hard)          288

SQL> select sql_text, executions, parse_calls from v$sql where sql_text like 'select /* test1 */%';

SQL_TEXT                       EXECUTIONS PARSE_CALLS
------------------------------ ---------- -----------
select /* test1 */ MY_SEQUENCE          3           3
_SEQ.nextval from dual

硬解析增加了 1 个并且 sql 已注册 3 个解析,因此 1 个硬解析(将其放入共享池)和 2 个软解析。

为什么要软解析?为了“不解析”发生​​,客户端代码必须保持语句句柄并重新执行它。即,如果我们用 Java 编写这个,我们会这样写:

    public static int getNextSeq(String str)
  throws Exception 
    {
        if (sel == null)
      {
        sel = con.prepareStatement("select MY_SEQUENCE_SEQ.nextval v from dual "+str);
      }
    ResultSet rs = sel.executeQuery();
    int seqVal=0;
    while (rs.next()) 
    {
      seqVal = rs.getInt("V");
    }
    return seqVal;
    }

即,如果我们还没有这样做,我们只会调用 PrepareStatement。如果我们执行这段代码

System.out.println(getNextSeq(args[0]));
System.out.println(getNextSeq(args[0]));
System.out.println(getNextSeq(args[0]));

我们可以看到这一点:

SQL> host java Prep two
70
71
72

SQL> select sql_text, executions, parse_calls from v$sql where sql_text like 'select %two';

SQL_TEXT                       EXECUTIONS PARSE_CALLS
------------------------------ ---------- -----------
select MY_SEQUENCE_SEQ.nextval          3           1
 v from dual two

现在 oracle 除了第 1 次硬解析外,还没有解析 SQL。如果 Java 代码写得不好,我们会看到:

sel = con.prepareStatement("select MY_SEQUENCE_SEQ.nextval v from dual "+str);
ResultSet rs = sel.executeQuery();


SQL> host java Prep three
73
74
75

SQL> select sql_text, executions, parse_calls from v$sql where sql_text like 'select %three';

SQL_TEXT                       EXECUTIONS PARSE_CALLS
------------------------------ ---------- -----------
select MY_SEQUENCE_SEQ.nextval          3           3
 v from dual three

现在我们看到解析计数 = 执行次数。换句话说,我们正在软解析每个不理想的调用。再次不是 Oracle 限制,只是糟糕的客户端实现。

使用 PL/SQL,我们不必担心这一点。为什么?PL/SQL 也不解析,因为它为运行 SQL 优化了很多(不出所料!)。例如:

SQL> declare
  2    v_seq number;
  3  begin
  4    for idx in 1..3 loop
  5      select MY_SEQUENCE_SEQ.nextval into v_seq from dual pls_test;
  6    end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> select sql_text, executions, parse_calls from v$sql where sql_text like 'SELECT %PLS_TEST';

SQL_TEXT                       EXECUTIONS PARSE_CALLS
------------------------------ ---------- -----------
SELECT MY_SEQUENCE_SEQ.NEXTVAL          3           1
 FROM DUAL PLS_TEST

现在,pl/sql 为我们做这个优化有一个警告,那就是参数 SESSION_CACHED_CURSORS。在给定的会话中,Oracle 将为我们打开一组游标(即它们是软打开的,也就是说,如果我们需要更多游标,它将关闭它们)。所以如果我们有 SESSION_CACHED_CURSORS=0 并重复上面的测试,我们会看到软解析突然出现:

SQL> alter session set session_cached_cursors=0;

Session altered.

SQL> declare
  2    v_seq number;
  3  begin
  4    for idx in 1..3 loop
  5      select MY_SEQUENCE_SEQ.nextval into v_seq from dual pls_test2;
  6    end loop;
  7  end;
  8  /

PL/SQL procedure successfully completed.

SQL> select sql_text, executions, parse_calls from v$sql where sql_text like 'SELECT %PLS_TEST2';

SQL_TEXT                       EXECUTIONS PARSE_CALLS
------------------------------ ---------- -----------
SELECT MY_SEQUENCE_SEQ.NEXTVAL          3           3
 FROM DUAL PLS_TEST2

显然,缓存游标的值越高,我们就越需要避免软解析并达到避免解析的圣杯。

于 2012-12-11T22:01:02.507 回答
1

这取决于您如何处理客户端的语句。如果您保留相同的变量/处理程序,则不应在每次调用时进行解析。


如果您在每次调用时创建并释放语句,您可以期望在共享池(软解析)或重新编译(硬解析)中搜索和找到 sql。
此外,您的下划线平台可以缓存在它的级别 - 以避免软解析。还有服务器参数文件来调整缓存会话游标的大小。

于 2012-12-11T19:23:26.003 回答