【问题标题】:Oracle 8i date function slowOracle 8i 日期功能慢
【发布时间】:2011-02-15 12:44:55
【问题描述】:

我正在尝试在 Oracle 8i 服务器(旧的,我知道)上运行以下 PL/SQL:

select
    -- stuff --
from
    s_doc_quote d,
    s_quote_item i,
    s_contact c,
    s_addr_per a,
    cx_meter_info m
where
    d.row_id = i.sd_id
    and d.con_per_id = c.row_id
    and i.ship_per_addr_id = a.row_id(+)
    and i.x_meter_info_id = m.row_id(+)
    and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
    and i.prod_id in ('1-QH6','1-QH8')
    and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate
;

然而,执行速度非常慢。由于服务器在每晚午夜左右关闭,因此通常无法及时完成。

执行计划如下:

SELECT STATEMENT   1179377
 NESTED LOOPS   1179377
  NESTED LOOPS OUTER  959695
   NESTED LOOPS OUTER  740014
    NESTED LOOPS   520332
     INLIST ITERATOR
      TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 157132
       INDEX RANGE SCAN S_QUOTE_ITEM_IDX8 8917
     TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
      INDEX UNIQUE SCAN S_DOC_QUOTE_P1 1
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
     INDEX UNIQUE SCAN S_ADDR_PER_P1 1
   TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1
  TABLE ACCESS BY INDEX ROWID S_CONTACT 1
   INDEX UNIQUE SCAN S_CONTACT_P1 1

如果我更改以下 where 子句:

and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate

转为静态值,如:

and d.created between to_date('20110101','yyyymmdd') and sysdate

执行计划变为:

SELECT STATEMENT   5
 NESTED LOOPS   5
  NESTED LOOPS OUTER  4
   NESTED LOOPS OUTER  3
    NESTED LOOPS   2
     TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
      INDEX RANGE SCAN S_DOC_QUOTE_IDX1 3
     INLIST ITERATOR
      TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 1
       INDEX RANGE SCAN S_QUOTE_ITEM_IDX4 4
    TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
     INDEX UNIQUE SCAN S_ADDR_PER_P1 1
   TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
    INDEX UNIQUE SCAN CX_METER_INFO_P1 1
  TABLE ACCESS BY INDEX ROWID S_CONTACT 1
   INDEX UNIQUE SCAN S_CONTACT_P1 1

几乎立即开始返回行。

到目前为止,我已经尝试用绑定变量替换动态日期条件,以及使用从对偶表中选择动态日期的子查询。到目前为止,这些方法都没有帮助提高性能。

因为我对 PL/SQL 比较陌生,所以我无法理解执行计划中出现如此巨大差异的原因。

我还尝试将查询作为 SAS 的传递来运行,但为了测试执行速度,我一直在使用 SQL*Plus。

编辑:

为了澄清,我已经尝试使用如下绑定变量:

var start_date varchar2(8);
exec :start_date := to_char(add_months(trunc(sysdate,'MM'), -1),'yyyymmdd')

使用以下 where 子句:

and d.created between to_date(:start_date,'yyyymmdd') and sysdate

返回 1179377 的执行成本。

如果可能,我还想避免绑定变量,因为我不相信我可以从 SAS 传递查询中引用它们(尽管我可能错了)。

【问题讨论】:

  • 这些表的优化器统计信息是最新的吗?定义了索引和外键等。无法从您的计划中真正看出,但可能是它无法为输出集计算出正确的基数,然后选择嵌套循环
  • @MikeyByCrikey - 查询设置为使用索引,但不幸的是我没有权限分析数据库/表统计信息。

标签: performance oracle date


【解决方案1】:

我怀疑这里的问题与 ADD_MONTHS 函数的执行时间有很大关系。您已经表明,当您使用硬编码的最小日期时,执行计划存在显着差异。执行计划的大变化对运行时间的影响通常比函数调用开销可能更大,尽管可能不同的执行计划可能意味着函数被调用更多次。无论哪种方式,要查看的根本问题是为什么您没有得到您想要的执行计划。

良好的执行计划始于对S_DOC_QUOTE_IDX1 的范围扫描。鉴于查询更改的性质,我假设这是CREATED 列上的索引。当过滤条件基于SYSDATE 时,优化器通常不会选择在日期列上使用索引。因为直到执行时间才被评估,所以在确定了执行计划之后,解析器无法很好地估计日期过滤条件的选择性。当您改用硬编码的开始日期时,解析器可以使用该信息来确定选择性,并对索引的使用做出更好的选择。

我也会建议绑定变量,但我认为因为您在 8i 上,优化器无法查看绑定值,所以这让它和以前一样处于黑暗中。在更高版本的 Oracle 中,我希望绑定解决方案会有效。

然而,这是一个很好的例子,使用文字替换可能比使用绑定变量更合适,因为 (a) 开始日期值不是用户指定的,并且 (b) 它将在整个月内保持不变,因此您不会解析许多略有不同的查询。

所以我的建议是编写一些代码来确定开始日期的静态值,并在解析和执行之前将其直接连接到查询字符串中。

【讨论】:

  • 感谢您的解释。由于您提到的 8i 限制,我决定在传递 SQL 以执行之前解析 SAS 中的文字日期值。
【解决方案2】:

首先,您获得不同执行时间的原因并不是因为 Oracle 经常执行日期函数。这个 SQL 函数的执行,即使它对每一行都执行(可能不是顺便说一句),与实际从磁盘/内存中检索行所需的时间相比,它只需要可忽略不计的时间。

您将获得完全不同的执行时间,因为正如您所注意到的,Oracle 选择了不同的访问路径。选择一个访问路径而不是另一个访问路径会导致执行时间出现数量级的差异。因此,真正的问题不是“为什么add_months 需要时间?”但是:

为什么甲骨文选择这条特定的低效路径,而有一条更有效的路径?

要回答这个问题,必须了解优化器的工作原理。 optimizer 通过估计几个访问路径的成本(如果只有几个表,则全部访问)并选择预期最有效的执行计划来选择特定的访问路径。确定执行计划成本的算法有规则,它根据从您的数据中收集的统计信息进行估计。

与所有估计算法一样,它会对您的数据做出假设,例如基于列的最小/最大值的一般分布、基数以及段中值的物理分布(聚类因子)。

这如何应用于您的特定查询

在您的情况下,优化器必须对不同过滤器子句的选择性进行估计。在第一个查询中,过滤器位于两个变量 (add_months(trunc(sysdate,'MM'), -1) and sysdate) 之间,而在另一种情况下,过滤器位于常量和变量之间。

它们在您看来是一样的,因为您已经用变量的值替换了变量,但对于优化器来说,情况却大不相同:优化器(至少在 8i 中)只为特定查询计算一次执行计划。一旦确定了访问路径,所有进一步的执行都将得到相同的执行计划。因此,它不能用变量的值替换变量,因为该值将来可能会发生变化,并且访问计划必须适用于所有可能的值。

由于第二个查询使用变量,优化器无法准确确定第一个查询的选择性,因此优化器会进行猜测,这会导致您的情况出现错误的计划。

当优化器没有选择正确的方案时你能做什么

如上所述,优化器有时会做出错误的猜测,从而导致访问路径不理想。即使它很少发生,这也可能是灾难性的(几小时而不是几秒)。以下是您可以尝试的一些操作:

  • 确保您的统计数据是最新的ALL_TABLESALL_INDEXES 上的 last_analyzed 列将告诉您最后一次收集这些对象的统计信息是什么时候。良好可靠的统计数据会带来更准确的猜测,从而(希望)带来更好的执行计划。
  • 了解收集统计信息的不同选项(dbms_stats 包)
  • 重写查询以在有意义的情况下使用常量,以便优化器做出更可靠的猜测。
  • 有时两个逻辑上相同的查询会导致不同的执行计划,因为优化器不会计算相同的访问路径(所有可能的路径)。
  • 您可以使用一些技巧来强制优化器在其他连接之前执行某些连接,例如:
    • 将rownum 用于materialize a subquery(可能会占用更多临时空间,但允许您强制优化器通过特定步骤)。
    • 使用hints,尽管大多数时候我只会在其他方法都失败时才求助于提示。特别是,我有时会使用 LEADING 提示来强制优化器从特定表(或几个表)开始。
  • 最后,您可能会发现较新的版本通常具有更可靠的优化器。 8i 已经 12 岁以上了,可能是时候升级了 :)

这真是一个有趣的话题。 oracle 优化器是不断变化的(在版本之间),它会随着时间的推移而改进,即使有时会在缺陷得到纠正时引入新的怪癖。如果你想了解更多,我建议 Jonathan Lewis 的Cost Based Oracle: Fundamentals

【讨论】:

  • 虽然我选择使用 Dave Costa 的文字替换建议,但这也是一个很好的答案。通过使用您提到的 LEADING 提示,我能够显示出一些改进。但最终,事实证明,使用文字日期值是最有效的。
【解决方案3】:

这是因为每次比较都会运行该函数。

有时将其放入双重选择中会更快:

and d.created 
    between (select add_months(trunc(sysdate,'MM'), -1) from dual) 
    and sysdate

否则,您也可以像这样加入日期:

select
    -- stuff --
from
    s_doc_quote d,
    s_quote_item i,
    s_contact c,
    s_addr_per a,
    cx_meter_info m,
    (select add_months(trunc(sysdate,'MM'), -1) as startdate from dual) sd
where
    d.row_id = i.sd_id
    and d.con_per_id = c.row_id
    and i.ship_per_addr_id = a.row_id(+)
    and i.x_meter_info_id = m.row_id(+)
    and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
    and i.prod_id in ('1-QH6','1-QH8')
    and d.created between sd.startdate and sysdate

最后一个选项,实际上是提高性能的最佳机会:向查询中添加日期参数,如下所示:

and d.created between :startdate and sysdate

[编辑] 对不起,我看到你已经尝试过这样的选择。还是很奇怪。如果常量值有效,则绑定参数也应该有效,只要您将 add_months 函数保留在查询之外。

【讨论】:

  • 谢谢,但是我已经尝试过类似于您建议的方法。我怀疑根本原因与您提到的函数被多次调用有关。
  • 当您使用绑定参数或加入选择时不应发生这种情况(建议 3 和 2 分别)。不过,甲骨文以神秘的方式工作。 :)
【解决方案4】:

这是 SQL。您可能希望使用 PL/SQL 并将计算 add_months(trunc(sysdate,'MM'), -1) 首先保存到变量中,然后绑定它。

此外,我发现 SAS 计算需要很长时间,因为它需要通过网络提取数据并在它处理的每一行上执行额外的工作。根据您的环境,您可以考虑先创建一个临时表来存储这些连接的结果,然后再访问该临时表(尝试 CTAS)。

【讨论】:

  • 谢谢,不过我已经尝试过绑定变量。关于使用 SAS,我确保一切都是通过传递查询完成的,以便只返回结果,并且在 SAS 端不进行任何处理。
猜你喜欢
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-25
  • 2011-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多