【问题标题】:Why is this Oracle query so slow?为什么这个 Oracle 查询这么慢?
【发布时间】:2020-03-19 01:43:47
【问题描述】:

简短的故事来解释我是如何陷入这个查询性能混乱的:
有人交给我一个旧的 Oracle 数据库来修复和维护。

数据模型设计得很糟糕,没有远见。
数据库状况糟糕,执行速度极慢。
当然,这个数据库已经超过 6 年没有调整或修改了......

我从这个数据库中的许多“快照”表之一开始。
(原始开发人员没有正确跟踪历史记录,而是将记录的快照计划复制并存储在各地的其他表中。因此您必须查询这些快照表以获取历史分析。)

此表有大约 100 列,但我们并不关心其中的大部分。
然而,当我几天前开始处理这个问题时,这个表有只有一个索引ID 列的非唯一索引,根本没有主键约束。

这是一个快照表,这意味着它包含来自不同时间点的行的历史副本。
例如,9/20/19 上的第 #12345 行,以及 9/30/19 上的第 #12345 行再次相同
所以,唉,ID 列必须允许重复值。

所以我想,第一步:在IDsnapshot_date 上创建一个复合主键约束,以创建一个正确的唯一标识符。

我正在尝试构建的分析查询是该应用程序中已经使用的许多不同的预先存在的查询的科学怪人。
它看起来像垃圾,因为这是我必须处理的......

select efh.snapshot_date,
       max(efhp.snapshot_date) as previous_snapshot_date,
       substr(efh.edge_vp,1,instr(efh.edge_vp,'@oracle.com')-1) as edge_vp,
       substr(efh.edge_rm,1,instr(efh.edge_rm,'@oracle.com')-1) as edge_rm,
       sum(case when efh.oppty_status = 'Open' then NVL(efh.ARR_FORECAST, 0) else 0 end) as forecast,
       sum(case when efh.oppty_status = 'Open' then NVL(efh.ARR_BEST,0) else 0 end) as best,
       sum(case when efh.oppty_status = 'Won' then NVL(efh.ARR,0) else 0 end) as closed,
       sum(case when efh.oppty_status = 'Open' then nvl(efh.ARR_PIPELINE,0) else 0 end) as pipeline,
       sum(case when efh.oppty_status = 'Open' then NVL(efh.ARR_BEST,0) else 0 end) +
       sum(case when efh.oppty_status = 'Open' then nvl(efh.ARR_PIPELINE,0) else 0 end) as pipe_best,
       sum(case when efh.oppty_status = 'Won' then efh.ARR else 0 end) +
       sum(case when efh.oppty_status = 'Open' then NVL(efh.ARR_FORECAST,0) else 0 end) as closed_forecast

  from edge_forecast_hist efh
  left join edge_forecast_hist efhp on efhp.edge_vp = efh.edge_vp and efhp.edge_rm = efh.edge_rm and efhp.snapshot_date < efh.snapshot_date
 where efh.snapshot_date >= TRUNC(sysdate) - INTERVAL '70' DAY
   and efh.edge_asm != 'REDACTED'
   and efh.oppty_status in ('Open', 'Won')

group by efh.snapshot_date,
         substr(efh.edge_vp,1,instr(efh.edge_vp,'@oracle.com')-1),
         substr(efh.edge_rm,1,instr(efh.edge_rm,'@oracle.com')-1)
order by 1, 2, 3, 4

一开始,这个查询需要大约 5 分钟来执行。

点击图片放大

在我创建主键以及此查询中引用的每一列的索引后,执行时间下降到约 4 分钟,这绝对是一个改进,但不如我预期的那么好。
(这个查询只返回几百行。)

当我尝试explain 这个查询时,我注意到实际上只有少数索引被使用。
(请参见上面的屏幕截图中的三个复选标记,指示正在使用哪些索引。)

执行计划中有一些令人不安的语言,
比如NESTED LOOPSTABLE ACCESS(表扫描??)

点击图片放大

这是一个糟糕的数据库,我是 Oracle 的新手,我并不完全了解执行计划中所有内容的细微差别。
这里的瓶颈似乎是什么,以及如何可能我减轻它?

想到的一些想法:

  • 自连接(这应该不是什么大问题,任何现代数据库都应该能够轻松处理)
  • 每列内嵌 case 语句,在选择列表中,内部聚合函数 (SUM)

如果真的有帮助,我可以做几个小时的工作,尝试将那些 case 语句分开。
(即在多个子查询中计算结果,将case 条件移到where 子句中。)
但我也认为 Oracle 足够聪明,可以找到最有效的执行计划,而不需要被灌输。
换句话说,我不想做所有这些工作,只是为了得到完全相同的执行计划。
我需要知道真正的问题是什么,这样我才能制定一个有针对性的解决方案来正确解决根本原因。

【问题讨论】:

  • 样本数据和期望的结果会有很大帮助。查询应该做什么?
  • @GordonLinoff 你好。 XD 感谢您的帮助和耐心。让我看看我是否可以为你收集结果。查询有效,我认为结果是准确的——只是速度很慢。给我几分钟来执行查询,我会发布一些结果。 (我必须编辑姓名和电子邮件地址等,所以可能需要一点时间。)
  • edge_vpedge_rm 中的值是什么?
  • @Nick 这些是VARCHAR 列,包含短文本值(大约 25 个字符,给或取)。其余列应该都是数字类型,主要是整数。
  • @oracle.com@oracle.com之后之后有什么东西吗?

标签: sql oracle performance query-performance sqlperformance


【解决方案1】:

我很确定您可以使用分析函数替换此查询,这样会快得多。

过滤使这有点棘手。一般来说,这意味着在子查询中运行窗口函数,然后在外部查询中进行过滤。但是,JOIN 条件可能会相互影响。

说了这么多,你想要的查询是这样的:

select efh.snapshot_date, efh.previous_snapshot_date,
       efh.edge_vp, efh.edge_rm,
       efh.forecast,
       . . .
from (select eft.*,
             lag(efh.snapshot_date) over (partition by efh.edge_vp, efh.edge_rm order by efh.snapshot_date) as previous_snapshot_date,
             substr(efh.edge_vp, 1, instr(efh.edge_vp,'@oracle.com')-1) as edge_vp,
             substr(efh.edge_rm, 1, instr(efh.edge_rm,'@oracle.com')-1) as edge_rm,
             sum(case when efh.oppty_status = 'Open' then efh.ARR_FORECAST else 0 end) over (partition by efh.edge_vp, efh.edge_rm order by efh.snapshot_date) as forecast,
             . . . 
      from edge_forecast_hist efh
     ) efh
where efh.snapshot_date >= TRUNC(sysdate) - INTERVAL '70' DAY and
      efh.edge_asm <> 'REDACTED' and
      efh.oppty_status in ('Open', 'Won');

附加列应遵循与forecast 相同的结构。

【讨论】:

  • 哇,这可能是一个重大改进。感谢您为我指明这个方向!让我看看我能不能实现这个,我会用结果回复你。
【解决方案2】:

这与@Gordon Linoff 有一些相似之处,但使用Rows Unbounded Preceding 的附加分析功能来创建一个累积和,这就是您的查询似乎要做的:

SELECT snapshot_date, previous_snapshot_date, edge_vp, edge_rm
, forecast, best, closed, pipeline
, best + pipeline AS pipe_best
, closed + forecast AS closed_forecast

FROM
(
  SELECT efh.snapshot_date
  , LAG(efh.snapshot_date) OVER (PARTITION BY efh.edge_vp, efh.edge_rm ORDER BY snapshot_date) as previous_snapshot_date
  , substr(efh.edge_vp,1,instr(efh.edge_vp,'@oracle.com')-1) as edge_vp
  , substr(efh.edge_rm,1,instr(efh.edge_rm,'@oracle.com')-1) as edge_rm
  , SUM(CASE WHEN efh.oppty_status = 'Open' THEN efh.ARR_FORECAST END) OVER (PARTITION BY efh.edge_vp, efh.edge_rm ORDER BY snapshot_date ROWS UNBOUNDED PRECEDING) as forecast
  , SUM(CASE WHEN efh.oppty_status = 'Open' THEN efh.ARR_BEST END) OVER (PARTITION BY efh.edge_vp, efh.edge_rm ORDER BY snapshot_date ROWS UNBOUNDED PRECEDING) as best
  , SUM(CASE WHEN efh.oppty_status = 'Won' THEN efh.ARR END) OVER (PARTITION BY efh.edge_vp, efh.edge_rm ORDER BY snapshot_date ROWS UNBOUNDED PRECEDING) as closed
  , SUM(CASE WHEN efh.oppty_status = 'Open' THEN efh.ARR_PIPELINE END) OVER (PARTITION BY efh.edge_vp, efh.edge_rm ORDER BY snapshot_date ROWS UNBOUNDED PRECEDING) as pipeline

  FROM edge_forecast_hist efh

  WHERE efh.snapshot_date >= TRUNC(sysdate) - INTERVAL '70' DAY
   AND efh.edge_asm != 'REDACTED'
   AND efh.oppty_status in ('Open', 'Won')
)

【讨论】:

  • ORA-00923: FROM keyword not found where expected
  • 应该修复-
  • 子查询有效吗?如果没有可重现的示例,很难调试。
  • 是的,子查询有效。我正在同时调试多个东西,抱歉延迟响应。
  • 好的。我进行了最后一次编辑,将SELECT * 替换为实际字段。现在应该修好了……
【解决方案3】:

只有一个版本 - 没有窗口函数(尽管它们一个好主意)但是 JOINing 有两个派生表(一个带有 efh.oppty_status = 'Open',另一个带有 efh.oppty_status = 'Won',以便去掉 SUM 中的所有条件。同时利用edge_vp 分组与substr(efh.edge_vp,1,instr(efh.edge_vp,'@oracle.com')-1) 分组相同的事实,因为@oracle.com 之后的字符串中没有任何内容。

select o.snapshot_date, 
       greatest(o.previous_snapshot_date, w.previous_snapshot_date) as previous_snapshot_date,
       substr(o.edge_vp,1,instr(o.edge_vp,'@oracle.com')-1) as edge_vp,
       substr(o.edge_rm,1,instr(o.edge_rm,'@oracle.com')-1) as edge_rm,
       o.forecast,
       o.best,
       w.closed,
       o.pipeline,
       o.best + o.pipeline AS pipe_best,
       w.closed + o.forecast AS closed_forecast
from (select efh.snapshot_date,
             max(efhp.snapshot_date) as previous_snapshot_date,
             efh.edge_vp,
             efh.edge_rm,
             sum(NVL(efh.ARR_FORECAST, 0)) as forecast,
             sum(NVL(efh.ARR_BEST,0)) as best,
             sum(nvl(efh.ARR_PIPELINE,0)) as pipeline
      from edge_forecast_hist efh
      left join edge_forecast_hist efhp on efhp.edge_vp = efh.edge_vp and efhp.edge_rm = efh.edge_rm and efhp.snapshot_date < efh.snapshot_date
      where efh.snapshot_date >= TRUNC(sysdate) - INTERVAL '70' DAY
        and efh.edge_asm != 'REDACTED'
        and efh.oppty_status = 'Open'
      group by efh.snapshot_date,
               efh.edge_vp,
               efh.edge_rm
      ) o
join (select efh.snapshot_date,
             max(efhp.snapshot_date) as previous_snapshot_date,
             efh.edge_vp,
             efh.edge_rm,
             sum(NVL(efh.ARR,0)) as closed
      from edge_forecast_hist efh
      left join edge_forecast_hist efhp on efhp.edge_vp = efh.edge_vp and efhp.edge_rm = efh.edge_rm and efhp.snapshot_date < efh.snapshot_date
      where efh.snapshot_date >= TRUNC(sysdate) - INTERVAL '70' DAY
        and efh.edge_asm != 'REDACTED'
        and efh.oppty_status = 'Won'
      group by efh.snapshot_date,
               efh.edge_vp,
               efh.edge_rm
      ) w ON w.snapshot_date = o.snapshot_date AND w.edge_vp = o.edge_vp AND w.edge_rm = o.edge_rm
order by 1, 2, 3, 4

【讨论】:

  • @Giffyguy 对错别字感到抱歉 - 感谢您的修正。
  • NP,还有一个错误ORA-00918: column ambiguously defined不知道如何调查那个。
  • @Giffyguy snapshot_date 需要从其中一个表中选择(例如o.snapshot_date),这应该可以修复错误。
  • 错误已修复!不幸的是,您的查询看起来仍然在某个地方遇到瓶颈。 :( 执行大约需要 2 分钟,这比我原来的大约 4 分钟有很大改进,但比 Cole 的解决方案要慢,后者在几秒钟内执行。
  • @Giffyguy 我很确定这是获取上一个快照日期的自我加入。切换到窗口功能肯定会改善这一点。无论如何,我很高兴你有一个好的解决方案。
猜你喜欢
  • 1970-01-01
  • 2013-06-04
  • 2011-03-11
  • 2014-03-12
  • 2011-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多