【问题标题】:CASE and COALESCE short-circuit evaluation works with sequences in PL/SQL but not in SQLCASE 和 COALESCE 短路评估适用于 PL/SQL 中的序列,但不适用于 SQL
【发布时间】:2013-12-31 06:40:19
【问题描述】:

CASECOALESCE() 文档中描述的短路评估是否适用于在 SQL 中使用的序列?这似乎没有发生。

Oracle documentation on CASE 声明:

Oracle 数据库使用短路评估。对于一个简单的CASE 表达式...如果先前的 compare_expr 等于 expr,Oracle 永远不会计算 compare_expr。对于搜索到的 CASE 表达式,如果前一个条件为真,数据库...从不计算条件。

COALESCE()the documentation 同样声明:

Oracle 数据库使用短路评估。数据库评估每个 expr 值并确定它是否为 NULL,而不是在确定它们中的任何一个是否为 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

SQL Fiddle.

但是,当从 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 sequencesthe SQL language reference on sequence psuedocolumnsthe PL/SQL language reference on CURRVAL and NEXTVALthe database concepts overview of sequences

CASECOALESCE() 在 SQL 中使用时是否会发生序列的短路评估?这有记录吗?

如果感兴趣,我们将在 11.2.0.3.5 上进行。

【问题讨论】:

  • 在 SQL Server 中测试。 NEXT VALUE FOR function cannot be used within CASE, CHOOSE, COALESCE, IIF, ISNULL and NULLIF. 我猜这是避免它的一种方法!
  • 我猜这是 Metalink 的问题。
  • 如果以前没有人注意到这一点,我会感到惊讶...@Alan。我设法在 metalink 上找到了 2331433,它看起来很相似(ish),但只在 CASE 而不是 COALESCE 上。我也喜欢 Oracle 的回复,但没有那么有帮助。

标签: sql oracle plsql oracle11g sequence


【解决方案1】:

对于 PL/SQL,Oracle 保证它将使用短路评估:

在计算逻辑表达式时,PL/SQL 使用短路 评估。也就是说,PL/SQL 一旦停止计算表达式 它可以确定结果。因此,您可以编写表达式 否则可能会导致错误。

发件人:2 PL/SQL Language Fundamentals

当您在 SQL 代码中使用nextval 时,我们遇到了不同的情况。

首先我们要记住currvalnextval是伪列:

伪列的行为类似于表列,但实际上并未存储 在表中。您可以从伪列中进行选择,但不能 插入、更新或删除它们的值。伪列也类似 到一个没有参数的函数(请参阅第 5 章, “职能”。但是,没有参数的函数通常返回 结果集中每一行的值相同,而伪列 通常为每一行返回不同的值。

发件人:3 Pseudocolumns

现在的问题是:为什么 Oracle 评估 nextval?还是在某处说明了这种行为?

在包含对 NEXTVAL 的引用的单个 SQL 语句中, Oracle 将序列递增一次:

  • 对于 SELECT 语句的外部查询块返回的每一行。这样的查询块可以出现在以下地方:

    1. 顶级 SELECT 语句
    2. INSERT ... SELECT 语句(单表或多表)。对于多表插入,对 NEXTVAL 的引用必须 出现在 VALUES 子句中,并且序列更新一次 子查询返回的每一行,即使 NEXTVAL 可能是 在多表插入的多个分支中引用。
    3. CREATE TABLE ... AS SELECT 语句
    4. CREATE MATERIALIZED VIEW ... AS SELECT 语句
  • 对于在 UPDATE 语句中更新的每一行

  • 对于每个包含 VALUES 子句的 INSERT 语句

  • 对于由 MERGE 语句合并的每一行。对 NEXTVAL 的引用可以出现在 merge_insert_clause 或 merge_update_clause 或 两个都。 NEXTVALUE 值随着每一行更新而递增 插入的每一行,即使序列号实际上没有使用 更新或插入操作。如果多次指定 NEXTVAL 在这些位置中的任何一个中,序列都会递增一次 每行并为所有出现的 NEXTVAL 返回相同的值 那一行。

发件人:Sequence Pseudocolumns

您的情况显然是“1.顶级SELECT语句”,但这并不意味着短路逻辑不到位,而只是始终评估nextval

如果您对短路逻辑感兴趣,那么最好从等式中删除nextval

这样的查询不会评估子查询:

select 6 c
  from dual
where  'a' = 'a' or 'a' = (select dummy from dual) 

但如果尝试对coalescecase 执行类似操作,我们将看到Oracle 优化器决定执行子查询:

select 6 c
  from dual
where  'a' = coalesce('a', (select dummy from dual) )

我在 this demo in SQLFiddle 中创建了带注释的测试来显示这一点。

看起来 Oracle 仅在 OR 条件下才应用短路逻辑,但对于 coalescecase,它必须评估所有分支。

我认为您在 PL/SQL 中的第一次测试表明 coalscecase 在 PL/SQL 中使用短路逻辑,正如 Oracle 所述。您的第二个测试,包括 SQL 语句中的序列,表明在这种情况下 nextval 无论如何都会被评估,即使结果未被使用,Oracle 也记录了这一点。

把这两件事放在一起看起来有点奇怪,因为 coalescecase 的行为似乎对我来说也确实不一致,但我们还必须记住,该逻辑的实现是依赖于实现的(@ 987654325@)

【讨论】:

  • I was able to convince the optimizer to not evaluate the sequence nextval 到底发生在哪里?抱歉,在您的 SQL 小提琴中找不到它。
  • 最后一个查询返回一个错误(ORA-02287:此处不允许序列号),即使此处允许使用数值(之前的查询使用CHR(64 +1))。所以我的解释是,但我很高兴知道有一个更好的解释,即对于最后一个查询,优化器的行为是不同的。 nextval 未评估,但这不允许 Oracle 编译查询。如您所见,我在SELECT FROM DUAL(第二次查询)和COALESCECHR(第四次查询)中使用nextval 没有问题,所以我得出的结论是,在子查询中nextval未评估
  • 不幸的是,它与短路评估无关。最后一个查询返回该错误只是因为它不允许在子查询中使用序列,并且documentation states it 非常清楚。
  • 你说得对,谢谢你指出这一点,我会用一些新的测试来修改我的答案
【解决方案2】:

为什么短路评估不适用于序列的解释可能如下。什么是序列?抛开内部,它是序列定义(记录在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 ...
CLOSE #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表已经两次被包含在执行计划中。

与子查询的类比是仓促的。当然,差异多于相似之处。序列是完全不同的机制。但是,优化器将序列视为行源,只要它没有看到在顶级查询的select 列表中直接引用的序列的nextval 伪列,它就不会t 评估序列,否则序列将递增,无论是否使用短路评估逻辑。显然,PL/SQL 引擎(从 Oracle 11g r1 开始)具有访问序列值的不同方式。需要注意的是,在以前的 11gR1 版本的 RDBMS 中,我们应该编写一个查询来引用 PL/SQL 块中的一个序列,该 PL/SQL 引擎直接发送到 SQL 引擎。

为什么在优化器生成执行计划期间序列会递增”问题的答案在于序列的内部实现。

【讨论】:

  • 我认为短路问题与序列 nextval 无关,它总是在 SELECT 语句中评估,根据Oracle documentation,但在casecoalesce 实现中Oracle SQL 引擎
  • 通过您更改的答案,您实际上表明 Nicholas 是正确的,似乎@mucio,您在暗示这是一个查询......这就是短路不起作用的原因。
  • 您的问题“在 SQL 中使用时,序列是否会发生 CASE 和 COALESCE() 的短路评估?这有记录吗?” Nicholas 说“在评估方面,直接在查询中引用序列,与包含相关子查询大致相同”。这不是真的,因为使用 WHERE... OR... (在我的演示中),您可以使用带有子查询的短路。我更正的答案是:Oracle 声明 SELECT 中的 nextval 总是被评估(并且只有一次),CASE/Coalesce 在 SELECT 语句(在简单 SQL 或 PL/SQL 块中)中的行为与在 PL/ 中用作函数不同SQL
  • @mucio 是的,说always 有点过分了。现在,where 子句完全是另一回事了。如何评估where 子句中的谓词完全取决于优化器。不能保证短路。 Here is an example 短路评估失败时。
  • @NicholasKrasnov 我认为在您的最后一种情况下,您遇到的情况类似于我的第一个答案中的情况,代码返回错误,但可能在调用优化器之前。我认为 (1/0) 是在进行任何表访问之前计算/检查的,如果您访问表,情况会有所不同:here for example
猜你喜欢
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多