執行計劃繫結

不一樣的天空w發表於2017-08-29



需要繫結SQL執行計劃常見的幾種情況:
SQL執行計劃突變,導致資料庫效能下降,從歷史執行計劃找一個合理的,進行繫結。
SQL無法使用更優的執行計劃,且無歷史執行計劃,可透過hint手工構造的方式,進行繫結。
某些Bug引起最佳化器生成較差的執行計劃。在bug修復前,進行繫結。

ORACLE固定執行計劃的3種方式:
Oracle 9i使用outline (可跨版本10,11g均可使用)
Oracle 10g使用sql profile (11g也可使用)
Oracle 11g使用sql plan manage

接下來簡述如何使用這3種方式進行執行計劃的固定,並舉例說明3種固定執行計劃的優缺點,透過對比選擇合適的固定執行計劃來應對不同的業務場景。也就是什麼場景下使用何種執行計劃固定比較合適。
 

一、大綱(Stored Outline)
語法:(手動建立outline)

CREATE [ OR REPLACE ]
   [ PUBLIC | PRIVATE ] OUTLINE [ outline ]
   [ FROM [ PUBLIC | PRIVATE ] source_outline ]
   [ FOR CATEGORY category ]
   [ ON statement ] ;

1、當SQL執行計劃因新版本變更,統計資訊不準確,新建索引,引數改變等發生改變時,儲存大綱可以使SQL語句的執行計劃保持不變。在建立某條語句的大綱時,ORACLE會將SQL語句的文字,執行計劃和語句使用的hints儲存在一個系統預設使用者OUTLN的3個表OL$,OL$HINTS,OL$NODES上。

2、使用大綱(outline)固定執行計劃
--環境構建,建立測試表
SQL> create table zw as select * from dba_objects where object_id is not null;

Table created.

SQL> explain plan for select count(*) from zw;

Explained.

SQL> set lines 200
SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
Plan hash value: 249608387

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |   339   (1)| 00:00:05 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| ZW   |   100K|   339   (1)| 00:00:05 |
-------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement (level=2)

13 rows selected.

--查詢資料字典dba_outlines:
SQL> select NAME,OWNER,CATEGORY,USED,SQL_TEXT,ENABLED,TIMESTAMP from dba_outlines;

no rows selected



--建立大綱(全表掃描),預設是私有outline

SQL> create or replace outline zwoutline for category mycate  on select count(*) from zw;

Outline created.

--再次查詢dba_outlines;
 col NAME for a10
 col OWNER for a10
 col CATEGORY for a10
 col SQL_TEXT for a30
 select NAME,OWNER,CATEGORY,USED,SQL_TEXT,ENABLED,TIMESTAMP from dba_outlines;

NAME       OWNER      CATEGORY   USED   SQL_TEXT                       ENABLED  TIMESTAMP
---------- ---------- ---------- ------ ------------------------------ -------- -------------------
ZWOUTLINE  SYS        MYCATE     UNUSED select count(*) from zw        ENABLED  2017-09-02 15:36:33



--建立object_id列索引,將該列屬性設定為非空
--索引不儲存null值  
SQL> alter table zw modify object_id not null;         

Table altered.

SQL> create index idx_zw_obj_id on zw(object_id);

Index created.

SQL> analyze table zw compute statistics;

Table analyzed.


SQL> explain plan for select count(*) from zw;

Explained.

SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1836624960

-------------------------------------------------------------------------------
| Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |               |     1 |    54   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |               |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| IDX_ZW_OBJ_ID |   100K|    54   (0)| 00:00:01 |
-------------------------------------------------------------------------------


--使用大綱固定執行計劃:(alter system/session set use_stored_outlines=mycate;)系統級或會話級別;

SQL> alter system set use_stored_outlines=mycate;

System altered.


--查詢dba_ouitlines(sql還未應用)

SQL>  select NAME,OWNER,CATEGORY,USED,SQL_TEXT,ENABLED,TIMESTAMP from dba_outlines;

NAME       OWNER      CATEGORY   USED   SQL_TEXT                       ENABLED  TIMESTAMP
---------- ---------- ---------- ------ ------------------------------ -------- -------------------
ZWOUTLINE  SYS        MYCATE     UNUSED select count(*) from zw        ENABLED  2017-09-02 15:36:33


--固定執行計劃之後,就會按照創大綱時的執行計劃去執行。
實際執行驗證:(實際執行後就會應用outline)
SQL> select count(*) from zw;

  COUNT(*)
----------
     87036

SQL>  select * from table(dbms_xplan.display_cursor(null, null, 'advanced'));

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------
SQL_ID  1f5n0rapts695, child number 0
-------------------------------------
select count(*) from zw

Plan hash value: 1836624960

-------------------------------------------------------------------------------
| Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |               |       |    54 (100)|          |
|   1 |  SORT AGGREGATE       |               |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| IDX_ZW_OBJ_ID | 87036 |    54   (0)| 00:00:01 |
-------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / ZW@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_FFS(@"SEL$1" "ZW"@"SEL$1" ("ZW"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


39 rows selected.

--查詢dba_outlines
select NAME,OWNER,CATEGORY,USED,SQL_TEXT,ENABLED,TIMESTAMP from dba_outlines;

NAME       OWNER      CATEGORY   USED   SQL_TEXT                       ENABLED  TIMESTAMP
---------- ---------- ---------- ------ ------------------------------ -------- -------------------
ZWOUTLINE  SYS        MYCATE     UNUSED select count(*) from zw        ENABLED  2017-09-02 15:54:31



上述的建立的大綱為公有大綱,為了不影響其它使用者的使用,可以建立私有大綱如下:

create or replace private outline zwoutline2 for category mycate2 on select count(*) from zw;

思考:為什麼我構建測試時,固定的是全表掃描,而不是比較最佳化的索引掃描?

其實這裡我想說明的是outline的缺點是比較死板的,當建立新的索引,或者資料量大幅度變化時是無法做出相應改變的,也就是說它是固定死的。

關於outline具體參考如下連結:
http://blog.csdn.net/whiteoldbig/article/details/17210079

從10g起,可以透過引用共享池中已經存在的SQL語句來建立outline
exec DBMS_OUTLN.create_outline(hash_value=>1752921103,child_number => 0,category=>‘test‘);
注意
  • 這種方法不能指定outline的具體名字,由系統自動生成,可以透過alter outline SYS_OUTLINE_14061114223605901 rename to dh_test2修改
  • 不指定類別的話預設為default,而且此處建立時,不能指定為default類別(會報錯)。
  • 我們使用outline固定執行計劃時,一般都是選用此種方法

參考:


實驗如下:
SQL>  create table dh_stat as select rownum id ,object_name name ,object_type type from dba_objects;

Table created.

SQL> create index ind_1 on dh_stat(id) compute statistics;                                          

Index created.

SQL> alter system flush shared_pool;

System altered.

SQL> exec DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'sys',TABNAME=>'dh_stat',ESTIMATE_PERCENT=>30,METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',NO_INVALIDATE=>FALSE,CASCADE=>TRUE,DEGREE => 1);

PL/SQL procedure successfully completed.

SQL> col name format a15
SQL> col name format a30
SQL> col sql_text for a55
SQL>  select /* outlinetest1 */ * from dh_stat where id=771;

        ID NAME                           TYPE
---------- ------------------------------ -------------------
       771 SYS_IL0000000772C00002$$       INDEX
       
SQL> set lines 200
SQL> select sql_text,sql_id,hash_value,child_number from v$sql a where sql_text like '%outlinetest1%' and sql_text not like '%v$sql%';

SQL_TEXT                                                SQL_ID        HASH_VALUE CHILD_NUMBER
------------------------------------------------------- ------------- ---------- ------------
 select /* outlinetest1 */ * from dh_stat where id=771  9a69w18a8uuhk  344812050            0
 
SQL>  select * from table(dbms_xplan.display_cursor('9a69w18a8uuhk',null));

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
SQL_ID  9a69w18a8uuhk, child number 0
-------------------------------------
 select /* outlinetest1 */ * from dh_stat where id=771

Plan hash value: 2780970545

---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| DH_STAT |     1 |    39 |     2   (0)| 00:00:01 |

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------
|*  2 |   INDEX RANGE SCAN          | IND_1   |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID"=771)


19 rows selected.

---根據sql已經有的執行計劃生成outline:

SQL> exec DBMS_OUTLN.create_outline(hash_value=>344812050,child_number => 0,category=>'TEST');        

PL/SQL procedure successfully completed.

--查詢outline情況:
SQL> select name,category,used,sql_text from dba_outlines;

NAME                           CATEGORY                       USED   SQL_TEXT
------------------------------ ------------------------------ ------ -------------------------------------------------------
SYS_OUTLINE_17090216454529101  TEST                           UNUSED  select /* outlinetest1 */ * from dh_stat where id=771

此處outline的USED狀態沒有改變,因為我們沒有啟用TEST類別的outline,再次申明,outline必須透過use_stored_outlines引數啟用後,最佳化器才會使用outline

---下面這一步啟用TEST類別的OUTLINE,也可以在系統級啟用OUTLINE
SQL> alter session set use_stored_outlines=TEST;

驗證省略。。。。。。。。。。。


二、SQL_PROFILE

1、DBMS_SQLTUNE是10g引入的一個新特性,它可以透過自動最佳化效能較差SQL,並給出合理的最佳化建議,其中最佳化建議中的sql_profile檔案它是一個儲存在資料字典中的資訊集合。sql_profile不包含單獨的執行計劃,提供資料庫配置、繫結變數、最佳化統計資訊、資料集等資訊供最佳化器選擇執行計劃。這裡不對SQL最佳化建議工具SQL Tuning Advisor STA)進行介紹,有興趣的童鞋研究一下DBMS_SQLTUNE包。


語法:
SQL text format
:

DBMS_SQLTUNE.CREATE_TUNING_TASK(
  sql_text         IN CLOB,
  bind_list        IN sql_binds := NULL,
  user_name        IN VARCHAR2  := NULL,
  scope            IN VARCHAR2  := SCOPE_COMPREHENSIVE,
  time_limit       IN NUMBER    := TIME_LIMIT_DEFAULT,
  task_name        IN VARCHAR2  := NULL,
  description      IN VARCHAR2  := NULL)
RETURN VARCHAR2;  

SQL ID format:

DBMS_SQLTUNE.CREATE_TUNING_TASK(
  sql_id           IN VARCHAR2,
  plan_hash_value  IN NUMBER    := NULL,
  scope            IN VARCHAR2  := SCOPE_COMPREHENSIVE,
  time_limit       IN NUMBER    := TIME_LIMIT_DEFAULT,
  task_name        IN VARCHAR2  := NULL,
  description      IN VARCHAR2  := NULL)
RETURN VARCHAR2;

Workload Repository format:

DBMS_SQLTUNE.CREATE_TUNING_TASK(
  begin_snap       IN NUMBER,
  end_snap         IN NUMBER,
  sql_id           IN VARCHAR2,
  plan_hash_value  IN NUMBER    := NULL,
  scope            IN VARCHAR2  := SCOPE_COMPREHENSIVE,
  time_limit       IN NUMBER    := TIME_LIMIT_DEFAULT,
  task_name        IN VARCHAR2  := NULL,
  description      IN VARCHAR2  := NULL)
RETURN VARCHAR2;

實驗1:sql_id format
1. sys使用者建立oracle最佳化任務(v_sqlid,task_name根據實際情況改變)
 set long 10000000 longchunksize 1000000 linesize 150 pagesize 0 serveroutput on size 1000000   verify off
 declare
  my_task_name varchar2(30);
  v_sqlid      varchar2(50);
 begin
  v_sqlid:='11wrxmug9y4a7';
  my_task_name := dbms_sqltune.create_tuning_task
                  (sql_id=> v_sqlid,
                   scope => 'comprehensive',
                   time_limit=>160,
                   task_name=>'task_00000',
                   description => 'tuning task');
  dbms_sqltune.execute_tuning_task('task_00000');              
  end;
 /
 
注:dbms_sqltune.execute_tuning_task('task_00000'),是執行最佳化任務
 
2. 列印最佳化任務,裡面有一些具體的改進措施
 select dbms_sqltune.report_tuning_task('task_00000') from dual;
 
3.根據最佳化建議,繫結profile,理論上可提高語句執行效率 
execute dbms_sqltune.accept_sql_profile(task_name => 'task_00000',task_owner => 'sys', replace => true); 

實驗2:sql_text format
SQL> alter session set statistics_level=all;

Session altered.

SQL> set serveroutput off
SQL> select * from scott.emp where ename='SCOTT' and DEPTNO=20;

     EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
      7788 SCOTT      ANALYST         7566 1987-04-19 00:00:00       3000                    20

SQL> SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL,'runstats_last'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  4dsqbp572auuu, child number 0
-------------------------------------
select * from scott.emp where ename='SCOTT' and DEPTNO=20

Plan hash value: 3956160932

------------------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |        |      1 |00:00:00.01 |       7 |
|*  1 |  TABLE ACCESS FULL| EMP  |      1 |      1 |      1 |00:00:00.01 |       7 |

PLAN_TABLE_OUTPUT
---------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(("ENAME"='SCOTT' AND "DEPTNO"=20))


18 rows selected.

SQL> DECLARE
 my_task_name VARCHAR2(30);
 my_sqltext   CLOB;
BEGIN
 my_sqltext := 'select * from scott.emp where ename= :name and DEPTNO= :deptno';
 my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
         sql_text    => my_sqltext,
bind_list   => sql_binds(anydata.convertvarchar2(10),anydata.convertnumber(2)),
         user_name   => 'SYS',
         scope       => 'COMPREHENSIVE',
         time_limit  => 60,
         task_name   => 'test_sql_tuning',
         description => 'Task to tune a query on emp');
END;
/

PL/SQL procedure successfully completed.

引數說明:
bind_list:多個繫結變數以','逗號分隔。引數值一定要根據繫結變數對應的列的型別書寫.如:emp.ename型別是VARCHAR2(10),那麼就要寫成 bind_list =>sql_binds(anydata.convertvarchar2(10)),
time_limit:執行的最長時間,預設是60。
scope:LIMITED,用大概1秒時間去最佳化SQL語句,但是並不進行SQL Profiling分析;COMPREHENSIVE,進行全面分析,包含SQL Profiling分析;比LIMITED用時更長。

--檢視任務名 
SQL> select task_name from dba_advisor_log where task_name='test_sql_tuning';

TASK_NAME
------------------------------
test_sql_tuning

--執行sql tuning任務
SQL> exec DBMS_SQLTUNE.EXECUTE_TUNING_TASK( task_name => 'test_sql_tuning' );

PL/SQL procedure successfully completed.

--檢視sql tunning任務狀態
SQL> select task_name,status from dba_advisor_log where task_name='test_sql_tuning';

TASK_NAME                      STATUS
------------------------------ -----------
test_sql_tuning                COMPLETED

----展示sql tunning結果
set long 10000
set longchunksize 1000
set linesize 100
select dbms_sqltune.report_tuning_task('test_sql_tuning') from dual;

...........省略

--根據建議accept_sql_profile
execute dbms_sqltune.accept_sql_profile(.....)

--完成後刪除sql tunning任務
exec dbms_sqltune.drop_tuning_task('test_sql_tuning');

--檢視SQL Tuning Advisor的進展(task執行很久)
set lines 200
col opname for a20
col ADVISOR_NAME for a20
select sid,serial#,username,opname,advisor_name,target_desc,start_time sofar,totalwork from v$advisor_progress where username = 'SYS'; 

三、coe_xfr_sql_profile.sql固定執行計劃(也是生成sql_profile)


--環境構建,建立測試表,與outline測試一樣

 create table zw as select * from dba_objects where object_id is not null;
 alter table zw modify object_id not null;          --索引不儲存null值
 create index idx_zw_obj_id on zw(object_id);
 analyze table zw compute statistics;
 
select count(*) from zw;                      

  COUNT(*)
----------
    87038
 
SQL> select * from table(dbms_xplan.display_cursor(null,0));
 
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------
SQL_ID  1f5n0rapts695, child number 0
-------------------------------------
select count(*) from zw

Plan hash value: 1836624960

-------------------------------------------------------------------------------
| Id  | Operation             | Name          | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |               |       |    54 (100)|          |
|   1 |  SORT AGGREGATE       |               |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| IDX_ZW_OBJ_ID | 87038 |    54   (0)| 00:00:01 |
-------------------------------------------------------------------------------

--使用hint提示,強制走全表,生成一個執行計劃:

SQL> select /*+ full(zw) */ count(*)from zw;
 
 COUNT(*)
----------
    87038
 
SQL> select * from table(dbms_xplan.display_cursor(null,0));

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------
SQL_ID  fp5ksbhww4594, child number 0
-------------------------------------
select /*+ full(zw) */ count(*)from zw

Plan hash value: 249608387

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |   339 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| ZW   | 87038 |   339   (1)| 00:00:05 |
-------------------------------------------------------------------


14 rows selected.


--執行coe_xfr_sql_profile指令碼:(固定執行計劃)

SQL> @coe_xfr_sql_profile.sql
 
Parameter 1:
SQL_ID (required)
 
Enter value for 1: 1f5n0rapts695                   --原sql的sql_id
 
 
PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
    1836624960          .02
 
Parameter 2:
PLAN_HASH_VALUE (required)
 
Enter value for 2: 249608387                      --想要選擇的執行計劃的PLAN_HASH_VALUE值
 
Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID         : "1f5n0rapts695"
PLAN_HASH_VALUE: "249608387"

SQL>BEGIN
  2    IF :sql_text IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20100, 'SQL_TEXT for SQL_ID &&sql_id. was not found in memory (gv$sqltext_with_newlines) or AWR (dba_hist_sqltext).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
SQL>BEGIN
  2    IF :other_xml IS NULL THEN
  3      RAISE_APPLICATION_ERROR(-20101, 'PLAN for SQL_ID &&sql_id. and PHV &&plan_hash_value. was not found in memory (gv$sql_plan) or AWR (dba_hist_sql_plan).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;

Execute coe_xfr_sql_profile_1f5n0rapts695_249608387.sql
on TARGET system in order to create a custom SQL Profile
with plan 249608387 linked to adjusted sql_text.


COE_XFR_SQL_PROFILE completed.

SQL> @coe_xfr_sql_profile_1f5n0rapts695_249608387.sql

省略.................
coe_xfr_sql_profile_1f5n0rapts695_249608387 completed

SQL> explain plan for select count(*) from zw;
 
Explained.
 
SQL> select * from table(dbms_xplan.display());
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 249608387
 
-------------------------------------------------------------------
| Id | Operation       | Name | Rows  | Cost (%CPU)| Time       |
-------------------------------------------------------------------
|   0| SELECT STATEMENT   |      |   1 |  312   (1)| 00:00:04 |
|   1|  SORT AGGREGATE    |            |   1 |          |            |
|   2|   TABLE ACCESS FULL| ZW   | 87038 |  312   (1)| 00:00:04 |
-------------------------------------------------------------------
 
Note
 
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
   -SQL profile "coe_1f5n0rapts695_249608387"used for this statement

--查詢:
SQL> select name,category,status,sql_text from dba_sql_profiles;
 
NAME                                CATEGORY     STATUS  SQL_TEXT
------------------------------      ------------      -------------------------------------------
coe_1f5n0rapts695_249608387    DEFAULE   ENABLED  select count(*) from zw

當存在只有一個執行計劃時,而又不想讓應用不改sql程式碼的情況下改變執行計劃,請參照自動/手動型別sql_profile(dbms_sqltune/coe_xfr_sql_profile.sql)


使用coe_xfr_sql_profile.sql固定計劃是不是很好用呢?是的,這一切都歸功於oracle mos上的功勞,需要的童鞋可以到matelink上查詢和下載。還有其它兩個有關的指令碼:coe_load_sql_baseline.sql,coe_load_sql_profile.sql,有興趣的童鞋可以一起下載研究。

思考:當在使用SQL_PROFILE繫結之前,使用了OUTLINE進行固定的話,誰的優先順序高呢?
根據網上的一些資料說是OUTLINE的優先順序最高,但都是簡短的一句話,沒有證明。可是經過我無數次的測試,發現都是SQL_PROFILE的優先順序較高,具體相關測試結果我就不貼上出來了。(或許是我測試語句的特殊性,需再進一步驗證)

值得一提的是,sql_profile並不會以outline方式儲存凍結執行計劃,當表中資料增長或索引被刪除或重建時,在sql_profile不變的情況下執行計劃也可以發生變化,資訊的儲存和與資料的分佈或者訪問路徑有關。


四、SQL PLAN MANAGE(baseline)

1、從11g開始,oracle引入了SQL執行計劃管理(SQLPlan Management)這個新特性,與Oracle 9i 的outline和10g 的profile相比,Oracle 11g的SPM相對更加的靈活,允許你同時接受多個執行計劃。


2、使用SQL PlanManagement固定執行計劃

--一條帶有繫結變數的SQL語句,但資料分佈不均,嚴重傾斜時,最好的執行計劃會根據繫結變數的值而不同。執行時,根據不同的變數值,SPM會花費很少的運算從中選擇一條最合適的。
SQL> select id,count(*) from test group by id order by 2;
 
         ID   COUNT(*)
---------- ----------
         10      1100
         88      10100
         999     1000000

--接下來定義一個變數a,分別賦值999和10,看它的執行計劃是如何的
SQL>alter system flush shared_pool;
SQL>var a1 number;
SQL>exec :a1:=999;
SQL>select t.* from test t wheret.id=:a1;
 
1000000 rows selected.
 
Elapsed: 00:00:25.30
 
SQL> select * from table(dbms_xplan.display_cursor(null,0));
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID   cpsdn05zdq02p,child number 0
-------------------------------------
select t.* from test t where t.id=:a1
 
Plan hash value: 1357081020
 
--------------------------------------------------------------------------
| Id | Operation      | Name | Rows | Bytes | Cost (%CPU)| Time   |
--------------------------------------------------------------------------
|   0| SELECT STATEMENT  |       |     |      |  424 (100)|      |
|*  1|  TABLE ACCESS FULL| TEST |   337K| 1316K|   424   (2)| 00:00:06 |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
 
Predicate Information (identified byoperation id):
---------------------------------------------------
 
   1- filter("T"."ID"=:A1)
 
-##########################ID列上有個索引IDX_ID ################################
SQL>alter system flush shared_pool;
SQL>var a1 number;
SQL>exec :a1:=10;
SQL>select t.* from test t wheret.id=:a1;

1100 rows selected.
 
Elapsed: 00:00:00.04
 
SQL> select * fromtable(dbms_xplan.display_cursor(null,0));
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID     cpsdn05zdq02p,child number 0
-------------------------------------
select t.* from test t where t.id=:a1
 
Plan hash value: 1357081020
 
--------------------------------------------------------------------------
| Id | Operation      | Name | Rows | Bytes | Cost (%CPU)| Time   |
--------------------------------------------------------------------------
|   0| SELECT STATEMENT  |       |     |      |  424 (100)|      |
|*  1|  TABLE ACCESS FULL| TEST |   337K| 1316K|   424   (2)| 00:00:06 |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
 
Predicate Information (identified byoperation id):
---------------------------------------------------
 
   1- filter("T"."ID"=:A1)
 
--這裡可以看到,無論賦值是999還是10,其執行計劃都是一樣的,但根據理論來說,我們都知道,id=10時走索引效率是最好的。假設資料是均勻分佈的,那麼基數評估cardinality=density*num_rows。Density可透過user_tab_col_statistics查詢。

select column_name,num_distinct,density from user_tab_col_statistics where table_name='TEST';
 
COLUMN_NAME                      NUM_DISTINCT    DENSITY
------------------------------    -------------   ---------
ID                                 3                .333333333

--我們看到的Rows列預估的337k就是cardinality=density*num_rows=0.3333*1011200約等於337k行,
--但是我們都知道ID=10只有1100行,而ID=999有1000000行,所以當ID=10的時候走索引全掃描,ID=999的時候走全表掃描是最合理的執行計劃。

那麼面對這種情況,我們該如何讓這種情況下的執行計劃達到最優呢?方法有如下幾個:
--1、去除繫結變數,直接硬解析的方式(非理想的,如果涉及要該程式程式碼這是很不可取的)
--2、啟用11g的新特性ACS(自適應遊標,這個BUG不是一般的多,不建議啟用)
--3、收集直方圖資訊(如果在生產高峰期,收集直方圖資訊所佔資源無法評估)
--4、使用SPM把不同的執行計劃加入到SQLPlan Baseline中。

--使用手工捕獲的方式
alter system flush shared_pool;
var a1 number;
exec :a1:=999;
select t.* from test t where t.id=:a1;
select * fromtable(dbms_xplan.display_cursor(null,0));

var temp varchar2(1000);
exec :temp:=dbms_spm.load_plans_from_cursor_cache(sql_id => 'cpsdn05zdq02p');
exec :temp :=dbms_spm.alter_sql_plan_baseline(sql_handle=>'SQL_d230ce970caa0077',plan_name=>'SQL_PLAN_d4c6fkw6an03r97bbe3d0',attribute_name=>'ENABLED',attribute_value=>'NO');         --先修改全表掃描的sql planbaselines的enabled屬性為NO,不然捕獲不了索引的。

exec :a1:=10;
select t.* from test t where t.id=:a1;
select * fromtable(dbms_xplan.display_cursor(null,0));

exec :temp:=dbms_spm.load_plans_from_cursor_cache(sql_id => 'cpsdn05zdq02p');
dbms_spm.alter_sql_plan_baseline(sql_handle=>'SQL_d230ce970caa0077',plan_name=>'SQL_PLAN_d4c6fkw6an03r97bbe3d0',attribute_name=>'ENABLED',attribute_value=>'YES');
 

SQL> select sql_handle,plan_name,origin,enabled,accepted,fixed from dba_sql_plan_baselines;
 
SQL_HANDLE        PLAN_NAME                        ORIGIN       ENA ACC FIX
-------------------------------------------------- -------------- --- --- ---
SQL_d230ce970caa0077SQL_PLAN_d4c6fkw6an03r97bbe3d0 MANUAL-LOAD   YES YES NO
SQL_d230ce970caa0077SQL_PLAN_d4c6fkw6an03rf98b55bb MANUAL-LOAD   YES YES NO

--驗證結果:
SQL> var a1 number;
SQL> exec :a1:=10;
SQL> select t.* from test t wheret.id=:a1;
SQL> select * fromtable(dbms_xplan.display_cursor(null,0));
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID     cpsdn05zdq02p,child number 0
-------------------------------------
select t.* from test t where t.id=:a1
 
Plan hash value: 578627003
 
---------------------------------------------------------------------------
| Id | Operation     | Name   | Rows | Bytes | Cost (%CPU)| Time         |
---------------------------------------------------------------------------
|   0| SELECT STATEMENT |            |     |     |   5(100)|     |
|*  1|  INDEX RANGE SCAN| IDX_ID |  1280 | 5120 |  5   (0)| 00:00:01 |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified byoperation id):
---------------------------------------------------
 
   1- access("T"."ID"=:A1)
 
Note
-----
   -SQL plan baseline SQL_PLAN_d4c6fkw6an03rf98b55bbused for this statement
 
22 rows selected.
 
SQL> var a1 number;
SQL> exec :a1:=999;
SQL> select t.* from test t wheret.id=:a1;
SQL> select * fromtable(dbms_xplan.display_cursor(null,0));
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID     cpsdn05zdq02p,child number 0
-------------------------------------
select t.* from test t where t.id=:a1
 
Plan hash value: 1357081020
 
--------------------------------------------------------------------------
| Id | Operation      | Name | Rows | Bytes | Cost (%CPU)| Time   |
--------------------------------------------------------------------------
|   0| SELECT STATEMENT  |       |     |      |  424 (100)|      |
|*  1|  TABLE ACCESS FULL| TEST |  1001K| 3912K|   424   (2)| 00:00:06 |
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified byoperation id):
---------------------------------------------------
 
   1- filter("T"."ID"=:A1)
 
Note
-----
   -SQL plan baseline SQL_PLAN_d4c6fkw6an03r97bbe3d0used for this statement
 
22 rows selected.


SPM的靈活之處在於,可以動態管理,不像儲存大綱(stored outline)和SQL Profile需要DBA手工建立,當然SPM也可以,因為我在以上演示中也沒讓它自動捕獲

思考:1、何種情況下使用什麼固定執行計劃的方法更加有效?2、在各種固定執行計劃都使用的情況下,那種優先順序更高?







來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31397003/viewspace-2144265/,如需轉載,請註明出處,否則將追究法律責任。

相關文章