我遇到Oracle为给定查询创建多个执行计划的情况。大多数时候,它选择一个表现相当不错的。但是,有时它会选择包含笛卡尔连接的连接,这是非常错误的。如果我们删除笛卡尔连接计划并使用其他计划之一运行查询,则表现良好,这表明基础数据确实不需要笛卡尔。
我们尝试收集统计数据并摆弄直方图,但似乎最终笛卡尔联合执行计划回来并间歇性地使用(有时需要数周或数月)。
Oracle是否可以禁用特定的执行计划?我们不能删除它,因为它似乎回来了,但是将它留在那里并将其禁用应该作为修复,但我不知道该怎么做或者是否可能。
答案 0 :(得分:5)
如评论中所述,使用SQL Plan Baselines是禁用执行计划的官方方法。它起作用,但是如下面的代码所示,它非常痛苦。
通过在加载数据之前创建对象并收集统计信息来创建错误的计划。
drop table bad_index;
create table bad_index(a number, b number);
create index bad_index on bad_index(a);
begin
dbms_stats.gather_table_stats(user, 'bad_index');
end;
/
insert into bad_index select level, level from dual connect by level <= 100000;
commit;
查询计划错误。它认为当查询真正返回100,000行时只有一行。当它应该使用HASH JOIN时,它使用NESTED LOOPS。
explain plan for
select count(*)
from bad_index bi1, bad_index bi2
where bi1.a = bi2.a
and bi1.a > 0;
select * from table(dbms_xplan.display);
Plan hash value: 4168051245
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 0 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 26 | | |
| 2 | NESTED LOOPS | | 1 | 26 | 0 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| BAD_INDEX | 1 | 13 | 0 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| BAD_INDEX | 1 | 13 | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("BI1"."A">0)
4 - access("BI1"."A"="BI2"."A")
filter("BI2"."A">0)
在没有explain plan for
的情况下运行上述查询以生成真正的SQL_ID。然后找到SQL_ID(5ukbyc726cdu3)。
select *
from gv$sql
where sql_text like '%bad_index bi1%'
and sql_text not like '%quine%'
and sql_text not like '%explain%';
使用该SQL_ID创建SQL Plan Baseline以捕获有关查询的信息。
declare
v_result pls_integer;
begin
v_result := dbms_spm.load_plans_from_cursor_cache(sql_id => '5ukbyc726cdu3');
end;
/
您可以在此处查看SQL计划基准。现在它只有一个计划:
select * from dba_sql_plan_baselines;
让我们通过收集统计数据并重新运行来生成更好的计划。
begin
dbms_stats.gather_table_stats(user, 'bad_index');
end;
/
select count(*)
from bad_index bi1, bad_index bi2
where bi1.a = bi2.a
and bi1.a > 0;
但是等一下,新的计划还没有完成。请注意,糟糕的计划仍在使用中。请注意Note
部分 - 它正在使用SQL计划基准。
explain plan for
select count(*)
from bad_index bi1, bad_index bi2
where bi1.a = bi2.a
and bi1.a > 0;
select * from table(dbms_xplan.display);
Plan hash value: 4168051245
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 100K (1)| 00:00:04 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
| 2 | NESTED LOOPS | | 100K| 976K| 100K (1)| 00:00:04 |
|* 3 | INDEX RANGE SCAN| BAD_INDEX | 100K| 488K| 201 (1)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| BAD_INDEX | 1 | 5 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("BI1"."A">0)
4 - access("BI1"."A"="BI2"."A")
filter("BI2"."A">0)
Note
-----
- SQL plan baseline "SQL_PLAN_fdcuj7gbjtgq561d678a6" used for this statement
现在SQL计划基线有两个计划。第一个被接受,更新,更好的计划不被接受。
select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines order by origin desc;
SQL_HANDLE PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq561d678a6 MANUAL-LOAD-FROM-CURSOR-CACHE YES YES NO
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq52b66d432 AUTO-CAPTURE YES NO NO
制定可能接受新计划的计划。函数的确切输出在这里并不重要,但如果您有好奇心,可以查看它。
declare
v_clob clob;
begin
v_clob := dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SQL_e6b3513bd71cbec5');
dbms_output.put_line(v_clob);
end;
/
再看看基线,它们都被接受了。
select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines order by origin desc;
SQL_HANDLE PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq561d678a6 MANUAL-LOAD-FROM-CURSOR-CACHE YES YES NO
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq52b66d432 AUTO-CAPTURE YES YES NO
为旧计划将ENABLED设置为NO,并为新计划将其设置为YES。如果新计划更好,这不是必要的,但这将确保永远不会使用旧计划。
declare
v_result pls_integer;
begin
v_result := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_e6b3513bd71cbec5', plan_name => 'SQL_PLAN_fdcuj7gbjtgq561d678a6', attribute_name => 'ENABLED', attribute_value => 'NO');
v_result := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_e6b3513bd71cbec5', plan_name => 'SQL_PLAN_fdcuj7gbjtgq52b66d432', attribute_name => 'ENABLED', attribute_value => 'YES');
end;
/
确认不再启用旧计划。
select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines order by origin desc;
SQL_HANDLE PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq561d678a6 MANUAL-LOAD-FROM-CURSOR-CACHE NO YES NO
SQL_e6b3513bd71cbec5 SQL_PLAN_fdcuj7gbjtgq52b66d432 AUTO-CAPTURE YES YES NO
现在查询将只使用更新,更好的计划,Rows
设置为100K并使用HASH JOIN而不是NESTED LOOP。
explain plan for
select count(*)
from bad_index bi1, bad_index bi2
where bi1.a = bi2.a
and bi1.a > 0;
select * from table(dbms_xplan.display);
Plan hash value: 544904072
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | | 278 (2)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 10 | | | |
|* 2 | HASH JOIN | | 100K| 976K| 1664K| 278 (2)| 00:00:01 |
|* 3 | INDEX FAST FULL SCAN| BAD_INDEX | 100K| 488K| | 57 (2)| 00:00:01 |
|* 4 | INDEX FAST FULL SCAN| BAD_INDEX | 100K| 488K| | 57 (2)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("BI1"."A"="BI2"."A")
3 - filter("BI1"."A">0)
4 - filter("BI2"."A">0)
Note
-----
- SQL plan baseline "SQL_PLAN_fdcuj7gbjtgq52b66d432" used for this statement
以上代码太可怕了。甲骨文真的放弃了这个系统,但这是&#34;官方&#34;这样做的方法。
通常最好避免使用SQL计划基准并找到其他解决方案。找出造成糟糕执行计划的原因并将其停止。