【问题标题】:Stored procedure SQL execution plan存储过程 SQL 执行计划
【发布时间】:2015-04-14 14:42:27
【问题描述】:

我对一个执行速度非常慢的存储过程有点卡住了。存储过程基本上包含一个使用传入参数 (in_id) 的查询,并像这样放在游标中:

open tmp_cursor for 
select col1, col2, col3
from table1 tab
where ((in_id is null) or (tab.id = in_id));  -- tab.id is the PK

当我使用预定义的值分别获得 SQL 查询的执行计划时,我使用索引的查询得到了很好的结果。但是,当我从我的应用程序中调用该过程时,我发现没有使用任何索引,并且该表被完全扫描,从而导致性能下降。
如果我删除 WHERE 子句“(in_id is null)”的第一部分,应用程序的性能又会很快。
为什么在我的应用程序调用期间没有使用索引(传入了in_id)?

【问题讨论】:

  • 如果查询假设在传入参数为空或匹配表 ID 的情况下返回数据,那么执行 if/else 语句是否会更好,如果它为空则它将运行不带 where 子句的查询,否则它将只使用表 ID 条件运行?

标签: sql oracle sql-execution-plan


【解决方案1】:

in_id 为空

我在这里https://stackoverflow.com/a/26633820/3989608回答了类似的问题

关于 NULL 值和 INDEX 的一些事实:

  • 完全 NULL 键未输入到 Oracle 中的“正常”B*Tree

  • 1234563在索引中。

Thomas Kyte 演示的部分内容:

ops$tkyte@ORA9IR2> create table t
2  as
3  select object_id, owner, object_name
4    from dba_objects;
Table created.

ops$tkyte@ORA9IR2> alter table t modify (owner NOT NULL);
Table altered.

ops$tkyte@ORA9IR2> create index t_idx on t(object_id,owner);
Index created.

ops$tkyte@ORA9IR2> desc t
Name                    Null?    Type
----------------------- -------- ----------------
OBJECT_ID                        NUMBER
OWNER                   NOT NULL VARCHAR2(30)
OBJECT_NAME                      VARCHAR2(128)

ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.

好吧,当应用于 OBJECT_ID 时,该索引当然可以用于满足“IS NOT NULL”:

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t where object_id is null;

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=34)
1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=34)
2    1     INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1)

事实上——即使表没有任何 NOT NULL 列,或者我们不希望/不需要包含 OWNER 的连接索引——也有一种透明的方法可以相当容易地找到 NULL OBJECT_ID 值:

ops$tkyte@ORA9IR2> drop index t_idx;
Index dropped.

ops$tkyte@ORA9IR2> create index t_idx_new on t(object_id,0);
Index created.

ops$tkyte@ORA9IR2> set autotrace traceonly explain
ops$tkyte@ORA9IR2> select * from t where object_id is null;

Execution Plan
----------------------------------------------------------
0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=34)
1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=34)
2    1     INDEX (RANGE SCAN) OF 'T_IDX_NEW' (NON-UNIQUE) (Cost=2 Card=1)

来源:Something about nothing by Thomas Kyte

【讨论】:

  • 您确定您理解正确吗,因为正如 ibre5041 所提到的,in_id 不是列名,而是存储过程的传入 IN 参数。所以我们不是在谈论通过索引在表中查找 NULL 值。还是我误会了?
【解决方案2】:

假设in_id 是一个查询参数——而不是一个列名:

无论输入如何,查询都必须只有一个执行计划。因此,如果您将参数 in_id 作为 NULL 传递,那么它应该返回所有行。如果你传递非 NULL in_id 应该只返回一个 PK 值。

所以甲骨文选择了“最差”的执行者。计划应对“最坏的可能”情况。 “通用”查询是通往地狱的道路。只需将查询一分为二。

select col1, col2, col3
from table1 tab
where in_id is null or in_id is not null;

这将使用全表扫描,这是获取所有行的最佳方式。

select col1, col2, col3
from table1 tab
where tab.id = in_id;  -- tab.id is the PK

这将使用 UNIQUE 索引扫描,这是获取单个索引行的最佳方式。

【讨论】:

  • 是的,这是正确的 - in_id 不是列名,而是 IN 参数。据我了解,优化器在不考虑传入值的情况下选择执行计划,从而使用比可能更糟糕的计划?在这种情况下,将查询拆分为 2 个单独的查询可能会出现问题,因为原始查询的 WHERE 子句中还有其他条件部分以相同的方式编写...
  • 原因是,查询是在输入参数已知之前编译和优化的。当 Oracle 看到输入变量不同时,它“不能”更改游标执行计划。 PS:因为 11g 有一个名为“绑定变量窥视”的选项,但在你的情况下,即使这也无济于事。 AFAIK Oracle 永远不会扫描 PK 索引以从表中获取所有行,当 in_id 为空时就是这种情况。您只是混淆了优化器,它无法预测查询将返回多少行。这对于优化器至关重要。
  • PS:请记住,PL/SQL 和纯 SQL 都是编译(优化)语言。它只是看起来像脚本,并且(除了绑定变量窥视)没有 JIT。所以编译后的“字节码”不能根据输入参数的性质进行自我调整。
【解决方案3】:
select col1, col2, col3 from table1 tab where (tab.id = nvl(in_id,tab.id));

可能会有所帮助.. 或者您可以使用 oracle 提示

+Use_concat

【讨论】:

    猜你喜欢
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 2015-06-16
    • 2010-10-05
    • 1970-01-01
    • 2011-11-11
    • 2010-11-03
    • 2015-09-01
    相关资源
    最近更新 更多