可以在Oracle中禁用SQL执行计划吗?

时间:2017-06-09 05:20:51

标签: sql database oracle database-performance sql-execution-plan

我遇到Oracle为给定查询创建多个执行计划的情况。大多数时候,它选择一个表现相当不错的。但是,有时它会选择包含笛卡尔连接的连接,这是非常错误的。如果我们删除笛卡尔连接计划并使用其他计划之一运行查询,则表现良好,这表明基础数据确实不需要笛卡尔。

我们尝试收集统计数据并摆弄直方图,但似乎最终笛卡尔联合执行计划回来并间歇性地使用(有时需要数周或数月)。

Oracle是否可以禁用特定的执行计划?我们不能删除它,因为它似乎回来了,但是将它留在那里并将其禁用应该作为修复,但我不知道该怎么做或者是否可能。

1 个答案:

答案 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计划基准并找到其他解决方案。找出造成糟糕执行计划的原因并将其停止。