【问题标题】:Oracle SQL: Understanding the behavior of SYS_GUID() when present in an inline view?Oracle SQL:了解内联视图中 SYS_GUID() 的行为?
【发布时间】:2014-05-21 00:43:34
【问题描述】:

这是有问题的示例 SQL; SQL 应该在任何 Oracle DBMS 上运行(我运行的是 11.2.0.2.0)。

请注意结果集中的 UUID 值是如何不同的(一个有 898,另一个有 899),尽管是从内联视图/with 子句中构建的。在下面您可以看到 DBMS_RANDOM.RANDOM() 是如何没有这种副作用的。

SQL:

WITH data AS (SELECT SYS_GUID () uuid FROM DUAL)
    SELECT uuid, uuid
      FROM data

输出:

UUID                                      UUID_1
F8FCA4B4D8982B55E0440000BEA88F11      F8FCA4B4D8992B55E0440000BEA88F11

对比 DBMS_RANDOM 结果是一样的

SQL:

WITH data AS (SELECT DBMS_RANDOM.RANDOM() rand FROM DUAL)
SELECT rand, rand
  FROM data

输出:

RAND    RAND_1
92518726    92518726

更有趣的是我可以通过调用 DBMS_RANDOM.RANDOM 来改变行为/稳定 sys_guid:

WITH data AS (
        SELECT SYS_GUID () uuid, 
        DBMS_RANDOM.random () rand 
        FROM DUAL)
SELECT uuid a,
       uuid b,
       rand c,
       rand d
  FROM data

稳定 SYS_GUID 的 SQL Fiddle: http://sqlfiddle.com/#!4/d41d8/29409

SQL Fiddle 这显示了奇怪的 SYS_GUID 行为: http://sqlfiddle.com/#!4/d41d8/29411

【问题讨论】:

  • 一直在玩这个 - 有趣的是,只有当您选择 DBMS_OUTPUT.RANDOM 的值两次或更多次时,该值才会稳定 - 请参阅sqlfiddle.com/#!4/d41d8/29447,我只使用该值一次并注意到 UUID 不再一样的……
  • 您可能想查看stackoverflow.com/questions/6776366/… 的前两个答案。 Oracle 在调用函数的方式上往往有点难以预测(部分原因在于它优化查询的方式),因此这个问题可能没有可靠的答案。
  • 可以看到不同的执行计划:SQl-Fiddle。 SQL-Server 的 NEWID() 具有类似的、不可预测的行为,具体取决于所选的执行计划。
  • 继续我的游戏 - 似乎“DBMS_RANDOM”作为一个名称在给出 SYS_GUID() 的“修复”行为方面有点神奇。我刚刚通过将系统 DBMS_RANDOM 包替换为包含返回数字“4”的单个函数的包来破坏本地​​安装,并且修复行为仍然存在 - 但调用同一个包“DBMS_RANDOM2”并且修复行为消失了......
  • this 是您指的 AskTom 帖子吗? (“我已经写了数千次了,你无法依赖 SQL 会调用你的函数的次数、时间或时间。”)

标签: sql oracle


【解决方案1】:

documentation gives a reason 关于为什么您可能会看到差异(强调我的):

注意:

因为 SQL 是一种声明性语言,而不是命令式(或过程式)语言,您无法知道 SQL 语句调用的函数将运行多少次——即使该函数是用PL/SQL,一种命令式语言。 如果您的应用程序要求某个函数执行一定次数,请不要从 SQL 语句调用该函数。请改用光标。

例如,如果您的应用程序要求为每个选定的行调用一个函数,则打开一个游标,从游标中选择行,然后为每一行调用该函数。这种技术保证调用函数的次数就是从游标中取出的行数。

基本上,Oracle 没有指定在 sql 语句中调用函数的次数:它可能取决于版本、环境、访问路径等因素。

但是,如Unnesting of Nested Subqueries 一章中所述,有一些方法可以限制查询重写:

子查询取消嵌套将子查询的主体取消嵌套并合并到包含它的语句的主体中,从而允许优化器在评估访问路径和连接时将它们一起考虑。优化器可以取消嵌套大多数子查询,有一些例外情况。这些例外包括分层子查询和包含 ROWNUM 伪列、集合运算符之一、嵌套聚合函数或对不是子查询的直接外部查询块的查询块的相关引用的子查询。

如上所述,您可以使用 ROWNUM 伪列来防止 Oracle 取消嵌套子查询:

SQL> WITH data AS (SELECT SYS_GUID() uuid FROM DUAL WHERE ROWNUM >= 1)
  2  SELECT uuid, uuid FROM data;

UUID                             UUID
-------------------------------- --------------------------------
1ADF387E847F472494A869B033C2661A 1ADF387E847F472494A869B033C2661A

【讨论】:

  • 我可以使用上面提到的ROWNUM 功能来自动增加ID 值(主键)吗?如果是,那么如果删除一行会发生什么? SYS_GUID 会自动更新吗?
  • @Satyadev 为什么不使用sequence 或新的IDENTITY column auto-increment
  • 这就是我最终在我的项目中使用的。谢谢
【解决方案2】:

NO_MERGE 提示“修复”它。防止 Oracle 重写内联视图。

WITH data AS (SELECT /*+ NO_MERGE */
                    SYS_GUID () uuid FROM DUAL)
SELECT uuid, uuid
  FROM data

From the docs:

NO_MERGE 提示指示优化器不要组合外部 查询和任何内联视图查询到单个查询中。此提示允许 您对访问视图的方式有更大的影响。

SQL Fiddle with the NO_MERGE hint applied:

我仍在努力理解/阐明如何以sys_guid() 将被调用两次的方式重写查询。也许这是一个错误;但我倾向于认为这是我自己的想法/代码中的一个错误。

【讨论】:

  • 我正在努力。不幸的是,我没有安装 oracle 来使用...但是会在一天内找到方法 :)
【解决方案3】:

非常有趣。

我们可以使用具体化提示来修复它。

WITH data AS (SELECT /*+materialize*/SYS_GUID () uuid FROM DUAL)
    SELECT uuid, uuid
      FROM data;

1   F9440E2613761EC8E0431206460A934C    F9440E2613761EC8E0431206460A934C

在我看来,如果我们可以通过添加提示来更改查询结果,则存在 Oracle 错误。 也许我们必须让metalink检查它......

【讨论】:

  • 如果查询不是确定性的,这不是错误。
  • 查询不是确定性的,但行为是确定性的!
  • 另外,/*+MATERIALIZE*/ 提示虽然有用,但没有记录:)
  • 我知道,但它节省了我很多时间! select * from V$SQL_HINT t where t.NAME='MATERIALIZE'
  • /*+ NO_MERGE */ 在我删除的答案中。这是一个记录在案的提示。
猜你喜欢
  • 1970-01-01
  • 2021-12-23
  • 2017-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-28
  • 2017-06-27
  • 1970-01-01
相关资源
最近更新 更多