我对执行速度非常慢的存储过程感到有些困惑。存储过程基本上包含一个使用传入参数(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为null)"应用程序的性能再次快速
为什么索引在我的应用程序调用期间没有被使用(传入in_id)?
答案 0 :(得分:2)
in_id为null
我在这里回答了类似的问题https://stackoverflow.com/a/26633820/3989608
有关NULL值和INDEX的一些事实:
完整的NULL键未输入Oracle中的“普通”B *树
因此,如果您在C1和C2上有连锁索引,那么您可能会在其中找到NULL值 - 因为您可能有一行其中C1为NULL但C2为非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)
实际上 - 即使表没有任何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)
答案 1 :(得分:1)
假设in_id
是查询参数 - 而不是列名:
无论输入如何,查询都必须只有一个exec计划。因此,如果将参数in_id
作为NULL传递,则应该返回所有行。如果传递非NULL in_id
则应该只返回一个PK值。
所以Oracle选择了最差的"#34; EXEC。计划处理"最坏的可能"场景。 "泛型"查询是通往地狱的道路。只需将查询拆分为两个。
select col1, col2, col3
from table1 tab
where in_id is null or in_id is not null;
这将使用FULL表扫描,这是获取所有行的最佳方法。
select col1, col2, col3
from table1 tab
where tab.id = in_id; -- tab.id is the PK
这将使用UNIQUE索引扫描,这是获取单个索引行的最佳方法。
答案 2 :(得分:0)
select col1, col2, col3 from table1 tab where (tab.id = nvl(in_id,tab.id));
可能有帮助......或者您可以使用oracle提示
+Use_concat