CASE
和COALESCE()
文档中描述的短路评估是否适用于SQL中的序列?这似乎没有发生。
Oracle documentation on CASE
声明:
Oracle数据库使用短路评估。对于简单的
CASE
表达式...如果先前的comparison_expr等于expr,则Oracle从不评估comparison_expr。对于搜索的CASE表达式,数据库...如果先前的条件为真,则永远不会评估条件。
同样,COALESCE()
the documentation表示:
Oracle数据库使用短路评估。数据库计算每个expr值并确定它是否为NULL,而不是在确定是否有任何值之前评估所有expr值。
从SQL调用序列时,似乎不是这种情况;正如您所看到的那样,没有发生短路,并且序列会增加。
SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> select tmp_test_seq.nextval from dual;
NEXTVAL
----------
1
SQL> select tmp_test_seq.currval from dual;
CURRVAL
----------
1
SQL> select coalesce(1, tmp_test_seq.nextval) from dual;
COALESCE(1,TMP_TEST_SEQ.NEXTVAL)
--------------------------------
1
SQL> select tmp_test_seq.currval from dual;
CURRVAL
----------
2
SQL> select case when 1 = 1 then 1 else tmp_test_seq.nextval end as s from dual;
S
----------
1
SQL> select tmp_test_seq.currval from dual;
CURRVAL
----------
3
但是,从PL / SQL调用时,序列不递增:
SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> declare
2 i number;
3 begin
4 i := tmp_test_seq.nextval;
5 dbms_output.put_line(tmp_test_seq.currval);
6 i := coalesce(1, tmp_test_seq.nextval);
7 dbms_output.put_line(i);
8 dbms_output.put_line(tmp_test_seq.currval);
9 i := case when 1 = 1 then 1 else tmp_test_seq.nextval end;
10 dbms_output.put_line(i);
11 dbms_output.put_line(tmp_test_seq.currval);
12 end;
13 /
1
1
1
1
1
SQL> select tmp_test_seq.nextval from dual;
NEXTVAL
----------
2
从PL / SQL调用SQL中的序列与SQL的结果相同:
SQL> create sequence tmp_test_seq start with 1 increment by 1;
SQL> declare
2 i number;
3 begin
4 select tmp_test_seq.nextval into i from dual;
5 dbms_output.put_line(tmp_test_seq.currval);
6 select coalesce(1, tmp_test_seq.nextval) into i from dual;
7 dbms_output.put_line(i);
8 dbms_output.put_line(tmp_test_seq.currval);
9 select case when 1 = 1 then 1 else tmp_test_seq.nextval end into i
10 from dual;
11 dbms_output.put_line(i);
12 dbms_output.put_line(tmp_test_seq.currval);
13 end;
14 /
1
1
2
1
3
文档中似乎没有任何关于此的内容; the Administrator's guide for managing sequences,the SQL language reference on sequence psuedocolumns,the PL/SQL language reference on CURRVAL and NEXTVAL或the database concepts overview of sequences。
在SQL中使用时,序列是否会发生CASE
和COALESCE()
的短路评估?这有记录吗?
如果感兴趣,我们的目标是11.2.0.3.5。
答案 0 :(得分:5)
对于PL / SQL,Oracle确保它将使用短路评估:
在评估逻辑表达式时,PL / SQL使用短路 评价。也就是说,PL / SQL会立即停止评估表达式 它可以确定结果。因此,您可以编写表达式 否则可能会导致错误。
来自:2 PL/SQL Language Fundamentals
在SQL代码中使用nextval
时,我们会遇到不同的情况。
首先,我们必须记住,currval
和nextval
是伪列:
伪列的行为类似于表列,但实际上并未存储 在表中。您可以从伪列中进行选择,但不能 插入,更新或删除其值。伪柱也是类似的 没有参数的函数(请参阅第5章, “功能”。但是,没有参数的函数通常返回 结果集中每一行的值相同,而伪列 通常为每一行返回不同的值。
来自:3 Pseudocolumns。
现在的问题是:为什么Oracle评估nextval
?或者这种行为是否在某处说明了?
在包含对NEXTVAL的引用的单个SQL语句中, Oracle将序列递增一次:
对于SELECT语句的外部查询块返回的每一行。这样的查询块可以出现在以下位置:
- 顶级SELECT语句
- INSERT ... SELECT语句(单表或多表)。对于多表插入,必须提及NEXTVAL 出现在VALUES子句中,序列更新一次 子查询返回的每一行,即使NEXTVAL可能是 在多表插入的多个分支中引用。
- CREATE TABLE ... AS SELECT语句
- CREATE MATERIALIZED VIEW ... AS SELECT语句
对于UPDATE语句中更新的每一行
对于包含VALUES子句的每个INSERT语句
对于MERGE语句合并的每一行。对NEXTVAL的引用可以出现在merge_insert_clause或merge_update_clause或 都。 NEXTVALUE值会针对更新的每一行递增 插入的每一行,即使实际上没有使用序列号 更新或插入操作。如果多次指定NEXTVAL 在任何这些位置,然后序列递增一次 每行并返回所有NEXTVAL for的相同值 那一行。
您的情况显然是“1.顶级SELECT语句”,但这并不意味着短路逻辑不到位,而只是nextval
总是被评估。
如果您对短路逻辑感兴趣,那么最好从等式中删除nextval
。
这样的查询不会评估子查询:
select 6 c
from dual
where 'a' = 'a' or 'a' = (select dummy from dual)
但如果尝试与coalesce
或case
做类似的事情,我们会看到Oracle Optimizer决定执行子查询:
select 6 c
from dual
where 'a' = coalesce('a', (select dummy from dual) )
我在this demo in SQLFiddle中创建了带注释的测试以显示此内容。
看起来Oracle仅在使用OR条件时应用短路逻辑,但是coalesce
和case
必须评估所有分支。
我认为你在PL / SQL中的第一次测试表明,coalsce
和case
在PL / SQL中使用短路逻辑,正如Oracle所述。您的第二个测试(包括SQL语句中的序列)表明,在这种情况下,无论如何都会评估nextval
,即使结果未被使用,Oracle也会对其进行记录。
把这两件事放在一起看起来有点奇怪,因为coalesce
和case
行为似乎也非常不一致,但我们也要记住,那个逻辑的实现是依赖于实现(here my source)
答案 1 :(得分:4)
短路评估不适用于序列的原因说明如下。什么是序列?抛开内部结构,它是序列定义(seq$
数据字典表中的记录)和一些内部SGA组件的组合,它不是一个函数,可能会被考虑,尽管文档does not state it directly(但执行计划确实如此) )作为行源。每次在查询的选择列表中直接引用序列时,优化器在搜索最佳执行计划时都必须对其进行评估。在形成最佳执行计划的过程中,如果引用nextval
伪列,则序列会递增:
SQL> create sequence seq1;
Sequence created
这是我们的序列:
SQL> select o.obj#
2 , o.name
3 , s.increment$
4 , s.minvalue
5 , s.maxvalue
6 , s.cache
7 from sys.seq$ s
8 join sys.obj$ o
9 on (o.obj# = s.obj#)
10 where o.name = 'SEQ1'
11 ;
OBJ# NAME INCREMENT$ MINVALUE MAXVALUE CACHE
---------- ------- ---------- ---------- ---------- ----------
94442 SEQ1 1 1 1E28 20
让我们跟踪查询,并查看其执行计划
SQL> ALTER SESSION SET EVENTS '10046 trace name context forever, level 4';
Session altered
SQL> select case
2 when 1 = 1 then 1
3 when 2 = 1 then seq1.nextval
4 end as res
5 from dual;
RES
----------
1
/* sequence got incremented by 1 */
SQL> select seq1.currval from dual;
CURRVAL
----------
3
跟踪文件信息:
STAT#1016171528 id = 1 cnt = 1 pid = 0 pos = 1 obj = 94442 op ='SEQUENCE SEQ1 ...
STAT#1016171528 id = 2 cnt = 1 pid = 1 pos = 1 obj = 0 op ='FAST DUAL ...
关闭#1016171528:c = 0,e = 12,dep = 0,type = 0,tim = 12896600071500 / *关闭光标* /
执行计划将向我们展示基本相同:
SQL> explain plan for select case
2 when 1 = 1 then 1
3 else seq1.nextval
4 end
5 from dual
6 /
Explained
Executed in 0 seconds
SQL> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
---------------------------------------------------------------
Plan hash value: 51561390
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | SEQUENCE | SEQ1 | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
9 rows selected
Executed in 0.172 seconds
在评估方面,直接在查询中引用序列,与包含相关子查询的内容大致相同。优化程序将始终评估相关的子查询:
SQL> explain plan for select case
2 when 1 = 1 then 1
3 when 2 = 1 then (select 1
4 from dual)
5 end as res
6 from dual;
Explained
Executed in 0 seconds
SQL> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------
Plan hash value: 1317351201
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
9 rows selected
Executed in 0.063 seconds
我们可以看到dual
表已包含在执行计划中两次。 击>
与子查询的类比是匆忙进行的。当然,差异多于相似之处。序列是绝对不同的机制。但是,优化器将序列视为行源,并且只要它没有看到在顶级查询的nextval
列表中直接引用的序列的select
伪列。 ,它不会评估序列,否则序列将递增,无论是否使用短路评估逻辑。显然,PL / SQL引擎(从Oracle 11g r1开始)有一种不同的方式来访问序列值。需要注意的是,在之前的11gR1版本的RDBMS中我们应该编写一个查询来引用PL / SQL块中的一个序列,其中PL / SQL引擎直接发送到SQL引擎。
“为什么序列在通过优化器生成执行计划期间增加”的答案在于序列的内部实现。