存储过程SQL执行计划

Stored procedure SQL execution plan

我有点受困于执行速度非常慢的存储过程。存储过程基本上包含一个使用传入参数 (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 查询的执行计划时,我通过使用索引的查询获得了良好的结果。但是,当我从我的应用程序调用该过程时,我发现没有使用索引并且 table 进行了全面扫描,因此性能下降。
如果我删除 WHERE 子句的第一部分“(in_id is null)”,应用程序的性能会再次变快。
为什么在我的应用程序调用期间不使用索引(传入 in_id)?

in_id is null

我在这里回答过类似的问题

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

  • Oracle 中的“正常”B* 树中不会输入完全为 NULL 的键

  • 因此,如果你在 C1 和 C2 上有一个连接索引,那么你可能会在其中找到 NULL 值——因为你可能有一行 C1 为 NULL 但 C2 为 NOT NULL –该键值将在索引中。

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)

事实上 – 即使 table 没有任何 NOT NULL 列,或者我们没有 want/need 有涉及 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

假设 in_id 是查询参数 - 不是列名:

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

因此 Oracle 选择了 "worst possible" exec。计划处理 "worst possible" 场景。 "generic" 查询是通往地狱之路。简单地将查询分成两部分。

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

这将使用完整 table 扫描,这是获取所有行的最佳方式。

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

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

select col1, col2, col3 from table1 tab where (tab.id = nvl(in_id,tab.id));

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

+Use_concat