存储过程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)
假设 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
我有点受困于执行速度非常慢的存储过程。存储过程基本上包含一个使用传入参数 (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)
假设 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