可以在 Oracle 中禁用 SQL 执行计划吗?
Possible to disable an SQL execution plan in Oracle?
我遇到过 Oracle 为给定查询创建多个执行计划的情况。大多数时候,它会选择某个表现相当不错的。然而,有时它会选择一个包含笛卡尔连接的连接,这是非常错误的。如果我们删除笛卡尔连接计划和 运行 与其他计划之一的查询,它表现良好,这对我来说表明底层数据确实不需要笛卡尔。
我们已经尝试收集统计数据并摆弄直方图,但似乎笛卡尔连接执行计划最终会恢复并间歇性地使用(有时这需要数周或数月)。
Oracle 中是否可以禁用特定的执行计划?我们不能因为它似乎又回来了就删除它,但是将它留在那里并禁用它应该可以作为一种修复方法,但我真的不知道该怎么做或者是否可行。
如评论中所述,使用 SQL 计划基线是禁用执行计划的官方方法。它有效,但如以下代码所示,它非常痛苦。
在加载数据之前创建对象并收集统计数据,从而制定一个糟糕的计划。
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;
查询计划错误。当查询真正 return 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 计划基线来捕获有关查询的信息。
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 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: 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
恭喜你做到了这一步
上面的代码太可怕了。 Oracle 在这个系统上确实失误了,但这是 "official" 的方法。
通常最好避免 SQL 计划基线并找到其他解决方案。找出导致错误执行计划的原因并停止它。
我遇到过 Oracle 为给定查询创建多个执行计划的情况。大多数时候,它会选择某个表现相当不错的。然而,有时它会选择一个包含笛卡尔连接的连接,这是非常错误的。如果我们删除笛卡尔连接计划和 运行 与其他计划之一的查询,它表现良好,这对我来说表明底层数据确实不需要笛卡尔。
我们已经尝试收集统计数据并摆弄直方图,但似乎笛卡尔连接执行计划最终会恢复并间歇性地使用(有时这需要数周或数月)。
Oracle 中是否可以禁用特定的执行计划?我们不能因为它似乎又回来了就删除它,但是将它留在那里并禁用它应该可以作为一种修复方法,但我真的不知道该怎么做或者是否可行。
如评论中所述,使用 SQL 计划基线是禁用执行计划的官方方法。它有效,但如以下代码所示,它非常痛苦。
在加载数据之前创建对象并收集统计数据,从而制定一个糟糕的计划。
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;
查询计划错误。当查询真正 return 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 计划基线来捕获有关查询的信息。
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 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: 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
恭喜你做到了这一步
上面的代码太可怕了。 Oracle 在这个系统上确实失误了,但这是 "official" 的方法。
通常最好避免 SQL 计划基线并找到其他解决方案。找出导致错误执行计划的原因并停止它。