这不是 Oracle 的错误,它只是 TOAD 向 oracle 发送 SQL 的方式。即toad 不会将语句句柄缓存到oracle,它只是在完成时关闭它。
当查询发送到 SQL 引擎时,Oracle 将对查询执行三项主要操作之一。
- 硬解析它
- 软解析它
- 不解析它
即我们想要在案例 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
显然,缓存游标的值越高,我们就越需要避免软解析并达到避免解析的圣杯。