須要綁定SQL執行計劃常見的幾種狀況:sql
SQL執行計劃突變,致使數據庫性能降低,從歷史執行計劃找一個合理的,進行綁定。數據庫
SQL沒法使用更優的執行計劃,且無歷史執行計劃,可經過hint手工構造的方式,進行綁定。session
某些Bug引發優化器生成較差的執行計劃。在bug修復前,進行綁定。oracle
ORACLE固定執行計劃的3種方式:ide
Oracle 9i使用outline (可跨版本10,11g都可使用)工具
Oracle 10g使用sql profile (11g也可以使用)性能
Oracle 11g使用sql plan manage測試
接下來簡述如何使用這3種方式進行執行計劃的固定,並舉例說明3種固定執行計劃的優缺點,經過對比選擇合適的固定執行計劃來應對不一樣的業務場景。也就是什麼場景下使用何種執行計劃固定比較合適。優化
1、大綱(Stored Outline)ui
一、當SQL執行計劃因新版本變動,統計信息不許確,新建索引,參數改變等發生改變時,存儲大綱可使SQL語句的執行計劃保持不變。在建立某條語句的大綱時,ORACLE會將SQL語句的文本,執行計劃和語句使用的hints存儲在一個系統默認用戶OUTLN的3個表OL$,OL$HINTS,OL$NODES上。
二、使用大綱(outline)固定執行計劃
--環境構建,創建測試表 create table zw as select * fromdba_objects where object_id is not null; explain plan for select count(*) from zw; 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 | 83926 | 312 (1)| 00:00:04 | ------------------------------------------------------------------- Note PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- -dynamic sampling used for this statement (level=2) --建立大綱(全表掃描) create or replace outline zwoutline forcategory mycate on select count(*) from zw; --建立object_id列索引,將該列屬性設置爲非空 alter table zw modify object_id not null; --索引不存儲null值 create index idx_zw_obj_id onzw(object_id); analyze table zw compute statistics; explain plan for select count(*) from zw; select * from table(dbms_xplan.display()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 1836624960 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0| SELECT STATEMENT | | 1 | 50 (0)| 00:00:01 | | 1| SORT AGGREGATE | | 1 | | | | 2| INDEX FAST FULL SCAN| IDX_ZW_OBJ_ID |79741 | 50 (0)| 00:00:01 | ------------------------------------------------------------------------------- --使用大綱固定執行計劃 alter system/session setuse_stored_outlines=mycate; --固定執行計劃以後,就會按照創大綱時的執行計劃去執行。
上述的創建的大綱爲公有大綱,爲了避免影響其它用戶的使用,能夠創建私有大綱以下:
create or replace private outlinezwoutline2 for category mycate2 on select count(*) from zw;
思考:爲何我構建測試時,固定的是全表掃描,而不是比較優化的索引掃描?
其實這裏我想說明的是outline的缺點是比較死板的,當建立新的索引,或者數據量大幅度變化時是沒法作出相應改變的,也就是說它是固定死的。
2、SQL_PROFILE
一、DBMS_SQLTUNE是10g引入的一個新特性,它能夠經過自動優化性能較差SQL,並給出合理的優化建議,其中優化建議中的sql_profile文件它是一個存儲在數據字典中的信息集合。sql_profile不包含單獨的執行計劃,提供數據庫配置、綁定變量、優化統計信息、數據集等信息供優化器選擇執行計劃。這裏不對SQL優化建議工具SQL Tuning Advisor STA)進行介紹,有興趣的童鞋研究一下DBMS_SQLTUNE包。
二、使用coe_xfr_sql_profile.sql固定執行計劃
--環境構建,創建測試表,與outline測試同樣 SQL> create table zw as select * fromdba_objects where object_id is not null; SQL> alter table zw modify object_id notnull; --索引不存儲null值 SQL> create index idx_zw_obj_id onzw(object_id); SQL> analyze table zw computestatistics; SQL> select count(*) from zw; -- INDEX FAST FULLSCAN COUNT(*) ---------- 79741 SQL> select * fromtable(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 | | | 50 (100)| | | 1| SORT AGGREGATE | | 1 | | | | 2| INDEX FAST FULL SCAN| IDX_ZW_OBJ_ID |79741 | 50 (0)| 00:00:01 | ------------------------------------------------------------------------------- SQL> select /*+ full(zw) */ count(*)from zw; --使用hint提示,強制走全表,生成一個執行計劃 COUNT(*) ---------- 79741 SQL> select * fromtable(dbms_xplan.display_cursor(null,0)); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------------ SQL_ID g5yd6rabqtdbp,child number 0 ------------------------------------- select /*+ full(zw) */ count(*) from zw Plan hash value: 249608387 ------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------- | 0| SELECT STATEMENT | | | 312 (100)| | | 1| SORT AGGREGATE | | 1 | | | | 2| TABLE ACCESS FULL| ZW | 79741 | 312 (1)| 00:00:04 | ------------------------------------------------------------------- SQL> @coe_xfr_sql_profile.sql Parameter 1: SQL_ID (required) Enter value for 1: 1f5n0rapts695 PLAN_HASH_VALUE AVG_ET_SECS --------------- ----------- 1836624960 .02 Parameter 2: PLAN_HASH_VALUE (required) Enter value for 2: 249608387 Values passed to coe_xfr_sql_profile: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SQL_ID : "1f5n0rapts695" PLAN_HASH_VALUE: "249608387" Executecoe_xfr_sql_profile_1f5n0rapts695_249608387.sql on TARGET system in order to create acustom SQL Profile with plan 249608387 linked to adjustedsql_text. COE_XFR_SQL_PROFILE completed. SQL> explain plan for select count(*)from zw; Explained. SQL> select * fromtable(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 | 79741 | 312 (1)| 00:00:04 | ------------------------------------------------------------------- Note PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------- -SQL profile "coe_1f5n0rapts695_249608387"used for this statement SQL> selectname,category,status,sql_text from dba_sql_profiles; NAME CATEGORY STATUS SQL_TEXT ------------------------------ ------------ ------------------------------------------- coe_1f5n0rapts695_249608387 DEFAULE ENABLED select count(*) from zw
使用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不變的狀況下執行計劃也能夠發生變化,信息的存儲和與數據的分佈或者訪問路徑有關。
3、SQL PLANMANAGE
一、從11g開始,oracle引入了SQL執行計劃管理(SQLPlan Management)這個新特性,與Oracle 9i 的outline和10g 的profile比,Oracle 11g的SPM相對更加的靈活,容許你同時接受多個執行計劃。
二、使用SQL PlanManagement固定執行計劃
--一條帶有綁定變量的SQL語句,但數據分佈不均,嚴重傾斜時,最好的執行計劃會根據綁定變量的值而不一樣。執行時,根據不一樣的變量值,SPM會花費不多的運算從中選擇一條最合適的。 SQL> select id,count(*) from test groupby 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 * 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) -##########################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,densityfrom 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 --的時候走全表掃描是最合理的執行計劃。那麼面對這種狀況,咱們該如何讓這種狀況下的執行計劃達 --到最優呢?方法有以下幾個: --一、去除綁定變量,直接硬解析的方式(非理想的,若是涉及要該程序代碼這是很不可取的) --二、啓用11g的新特性ACS(這個BUG不是通常的多,不建議啓用) --三、收集直方圖信息(若是在生產高峯期,收集直方圖信息所佔資源沒法評估) --四、使用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、在各類固定執行計劃都使用的狀況下,那種優先級更高?