【问题标题】:Query Tuning in stored procedure存储过程中的查询调优
【发布时间】:2019-09-06 18:02:26
【问题描述】:

我需要调整下面提到的这个查询,以避免每次都与存档表联合,因为下面的查询可能会不必要地扫描存档表,即使在大多数情况下数据将出现在第一个表本身中。

Procedure status(p_order_id in varchar2(25), p_stat out sys_refcursor) IS

Begin
open p_stat

select o.status_code,o.order_id FROM order o
where o.order_id=p_order_id 
union
select a.status_code,a.order_id FROM order_archive a
where a.order_id=p_order_id ;

end status;

【问题讨论】:

  • 您的查询中没有FROM 子句,还是我累了?
  • 如果order_id 在两个表中都被索引(并且具有高度选择性),我不确定它会产生多大的不同,不是吗?或者“扫描”是否表明存档表没有被索引?您是否真的有性能问题,或者您只是认为这看起来有问题?
  • Alex,订单ID是索引。

标签: oracle stored-procedures oracle11g sql-tuning


【解决方案1】:

您可以在主表中添加not exists 检查作为归档分支的一部分:

select o.status_code,o.order_id
from order o
where o.order_id=p_order_id 
union all
select a.status_code,a.order_id
from order_archive a
where a.order_id=p_order_id
and not exists (
  select *
  from order o
  where o.order_id=p_order_id 
);

当然,这意味着它将查看两个分支中的主表;但它可能仍然更快,具体取决于您的数据、索引、统计信息等。


另一个选项,如果 ID 存在于主表中,将阻止查看存档表,它是先对主表进行简单计数,然后有条件地打开 ref 游标以针对两个可能的查询之一每人一张桌子;即在 PL/SQL 中而不是在 SQL 中分支,完全消除联​​合。比如:

procedure status(p_order_id in varchar2, p_stat out sys_refcursor) is
  l_count pls_integer;
begin
  select count(*) into l_count
  from order o
  where o.order_id=p_order_id;

  if l_count > 0 then
    -- exists in main table so only query that
    open p_stat for
      select o.status_code,o.order_id
      from order o
      where o.order_id=p_order_id;
  else
    -- does not exist in main table so only query archive
    open p_stat for
      select a.status_code,a.order_id
      from order_archive a
      where a.order_id=p_order_id;
  end if;
end status;
/

如果 ID 在主表中不是唯一的,那么您可以将 and rownum = 1 添加到初始查询中,以便在找到任何匹配的行后立即停止(因此 l_count 最多可以为 1)而不是获得准确的计数,因为您并不真正关心找到的实际数字。

我们可以在订单表的数据集上使用 fetch 和 loop 吗?如果找到然后只去存档表

并非如此,因为 fetch 会消耗结果的第一行。这是一个非常人为的例子:

var rc refcursor;

declare
  x varchar2(1);
begin
  open :rc for
    select 'A' from dual where 1 = 0
    union all
    select 'B' from dual where 1 = 0;
  fetch :rc into x;
  if :rc%notfound then
    open :rc for select 'C' from dual;
  end if;
end;
/

PL/SQL procedure successfully completed.

print rc

'
-
C

使用此设置,“主”查询找不到任何行(因为 1 = 0 检查);获取后游标是notfound,因此它会重新打开“归档”查询的游标,调用者会看到预期的结果。

但如果第一个查询确实返回行:

declare
  x varchar2(1);
begin
  open :rc for
    select 'A' from dual where 1=1
    union all
    select 'B' from dual where 1=1;
  fetch :rc into x;
  if :rc%notfound then
    open :rc for select 'C' from dual;
  end if;
end;
/

PL/SQL procedure successfully completed.

print rc

'
-
B

然后在 fetch 之后,游标是 found,因此它不会为归档查询重新打开游标,并且现有的 ref 游标会被传递回调用者。但是游标的第一行已经被提取到x 变量中并且丢失了,所以调用者不再看到它。 print 仅显示带有'B' 的单行,而另一行应该带有'A' 的行丢失了。

要让调用者仍然看到这两行,您必须使用“主”查询重新打开游标:

declare
  x varchar2(1);
begin
  open :rc for
    select 'A' from dual where 1=1
    union all
    select 'B' from dual where 1=1;
  fetch :rc into x;
  if :rc%found then
    open :rc for
      select 'A' from dual where 1=1
      union all
      select 'B' from dual where 1=1;
  else
    open :rc for select 'C' from dual;
  end if;
end;
/

PL/SQL procedure successfully completed.

print rc

'
-
A
B

我想,从逻辑上讲,这实际上与我在上面所做的初始计数相同,但不太清楚。

【讨论】:

  • Alex,我们可以在其中使用一些临时变量并完全避免任何联合。如果我们在那个临时值中没有任何值,那么只去扫描 Arc 表。因为现在执行时间得到了改善,但还没有达到我想要的程度。
  • @SudiptoSarkar - 我认为只有将存在检查移到单独的查询中。我不确定这也会有很大的不同。不过我已经添加了一个示例。
  • 在我的情况下,90% 的时间我会从订单表中获取结果,而不是去存档。我们可以在订单表的数据集上使用获取和循环吗?如果找到然后只去存档表
  • 谢谢 Alex,让我看看它改进了执行计划,但这看起来很有希望。
  • 您无法打开光标并检查它发现的内容来决定是否将其传回或重新打开存档;因为这样做会通过 fetch 消耗游标的第一个结果。您必须重新打开游标 - 并重新查询主表 - 以便调用者取回所有数据。最终结果与我添加的独立检查大致相同,但代码更多且更复杂,不会节省任何时间。
【解决方案2】:

第一步很容易使用UNION ALL,因为UNION保证了两个表的distinct结果。

使用UNION ALL 将从第一个表中获取数据,并且只有在从中获取所有行后才会访问第二个表。

UNION 只需要,如果两个表中的 order_idstatus_code 是相同的行,并且您需要对它们进行重复数据删除;这不是典型的场景。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多