文章目錄
- 0、參考資料
- 1、查看執行計劃&指標解讀
- 1.1、查看執行計劃
- 1.1.1、explain plan命令
- 1.1.2、DBMS_XPLAN包
- 1.1.3、DISPLAY_SQL_PLAN_BASELINE函數
- 1.1.4、SQLPLUS中的AUTOTRACE
- 1.1.5、其它方式
- 1.1.6、如何得到最真實的執行計劃
- 1.2、如何讀懂執行計劃
- 2、穩固執行計劃
0、參考資料
書籍:
崔華大師的《基於SQL優化》
hello dba的《SQL based CBO》
Jonathan Lewis的《Cost Based Oracle Fundamentals》
梁敬彬、梁敬泓兄弟的《收穫,不止SQL優化--抓住SQL的本質》
官方文檔:
performing tuning guide -> Using EXPLAIN PLAN
Database PL/SQL Packages and Types Reference -> DBMS_XPLAN
1、查看執行計劃&指標解讀
1.1、查看執行計劃
1.1.1、explain plan命令
explain plan for select * from t_users where user_id=:A;
select * from table(dbms_xplan.display);
說明:在Oracle 10g及其以上的版本里,對目標SQL執行explain plan命令,則Oracle就將解析目標SQL所產生的執行計劃的具體步驟寫入PLAN_TABLE中將這些具體執行步驟以格式化方式顯示出來。
PLAN_TABLE$表是一個ON COMMIT PRESERVE ROWS的GLOBAL TEMPORARY TABLE,所以這裏Oracle可以做到各個session只能看到自己執行的SQL所產生的執行計劃,並且互不干擾。
1.1.2、DBMS_XPLAN包
–方法1
select * from table(dbms_xplan.display);
–方法2
select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
–方法3,常用,最好方式
select * from table(dbms_xplan.display_cursor('sql_id/hash_value',child_cursor_number/null,'advanced'));
–方法4,從awr中查看歷史執行計劃
select * from table(dbms_xplan.display_awr('sql_id',plan_hash_value/null));
–方法5,從sqlset中提取
select * from table(dbms_xplan.display_sqlset('STS_2','d76shb2rbmmsn',null,'BASIC ROWS COST'));
方法1需要和上面的explain plan命令配合使用;
方法2它用於在sqlplus中查看剛剛執行過的SQL執行計劃。這裏針對方法DBMS_XPLAN.DISPLAY_CURSOR所傳入的第一個和第二個參數的值均爲null,第三個參數的值是advanced,第三個輸入參數值也可以是all,只不過用advanced後顯示的結果會比用all的顯示結果更詳細一些。
方法3這裏針對方法DBMS_XPLAN.DISPLAY_CURSOR所傳入的第一個參數的值是指定SQL的SQL ID或者SQL hash value,第二個參數的值是要查看的執行計劃所在的child cursor number。
方法4是從awr報告中查看歷史執行計劃信息。但是awr中把執行計劃中的謂詞條件省掉了,這點非常不好。要正常調用DISPLAY_AWR 參數,必須對以下視圖有權限:DBA_HIST_SQL_PLAN 和DBA_HIST_SQLTEXT 的SELECT方法2、3只能從當前sga shared pool中查看到,如果被sga out,就看不到了
。
方法5 sqlset方式:DISPLAY_SQLSET 函數顯示存儲在一個SQL 調優集中的語句的執行計劃。
declare
ss_name varchar2(30);
begin
ss_name := dbms_sqltune.create_sqlset();
dbms_sqltune.capture_cursor_cache_sqlset(ss_name,300,100);
dbms_output.put_line(ss_name);
end;
/
STS_2
PL/SQL procedure successfully completed.
select sqlset_name,sql_id,sql_text from DBA_SQLSET_STATEMENTS where upper(sql_text)
like 'SELECT * FROM T_USERS%';
select * from table(dbms_xplan.display_sqlset('STS_2','d76shb2rbmmsn',null,'BASIC ROWS COST'));
1.1.3、DISPLAY_SQL_PLAN_BASELINE函數
DISPLAY_SQL_PLAN_BASELINE 函數顯示存儲在數據字典當中SQL 執行計劃基線的計劃。
select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle =>'SYS_SQL_99cc41808e350a83'));
1.1.4、SQLPLUS中的AUTOTRACE
說明:要使用AUTOTRACE,需要先做以下準備,用DBA 用戶創建角色PLUSTRCE,並將該角色賦予用戶;使用autotrace查看執行計劃時,實際sql語句已經被執行了,這點需要小心,特別是獲取一些dml語句執行計劃的時候。
conn sys/sys as sysdba
Connected.
@?/SQLPLUS/ADMIN/PLUSTRCE.SQL
grant plustrace to demo;
SQL>SET TIMING ON --控制顯示執行時間統計數據
SQL>SET AUTOTRACE ON EXPLAIN --這樣設置包含執行計劃、腳本數據輸出,沒有統計信息
SQL>set autotrace trace explain
SQL>執行需要查看執行計劃的SQL語句
SQL>SET AUTOTRACE OFF --不生成AUTOTRACE報告,這是缺省模式
SQL> SET AUTOTRACE ON --這樣設置包含執行計劃、統計信息、以及腳本數據輸出
SQL>執行需要查看執行計劃的SQL語句
SQL>SET AUTOTRACE OFF
SQL> SET AUTOTRACE TRACEONLY --這樣設置會有執行計劃、統計信息,不會有腳本數據輸出
SQL>執行需要查看執行計劃的SQL語句
SQL>SET AUTOTRACE TRACEONLY STAT --這樣設置只包含有統計信息
SQL>執行需要查看執行計劃的SQL語句
1.1.5、其它方式
10046/10053事件、圖形化工具、寫腳本從VSQL_PLAN_MONITOR等視圖中來獲取執行計劃等,因爲不常用,不再一一列舉
1.1.6、如何得到最真實的執行計劃
DBMS_XPLAN包查看執行計劃的章節中,方法2、方法3、方法4都是可以獲取真實執行計劃的方法,因爲sql都實際執行過了。
執行計劃中顯示實際行數(A-Rows)和執行次數(Starts),調優常用
執行sql前提條件:
-
一般在會話級別設置參數STATISTICS_LEVEL爲ALL,也可以使用/+ GATHER_PLAN_STATISTICS/提示。
-
若DBMS_XPLAN.DISPLAY_CURSOR中的入參SQL_ID輸入值爲NULL的話,則SERVEROUTPUT必須設置爲OFF(SET SERVEROUTPUT OFF),否則會報錯。
步驟如下:
SET SERVEROUTPUT OFF
ALTER SESSION SET STATISTICS_LEVEL=ALL;
設置完成後執行SQL語句
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ADVANCED ALLSTATS LAST'));
或
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(SQL_ID =>'',CURSOR_CHILD_NO =>1,FORMAT => 'ADVANCED ALLSTATS'));
DBMS_XPLAN.DISPLAY_CURSOR參數解釋如下:
其中參數SQL_ID爲父遊標,如果爲NULL,那麼表示顯示該會話之前的SQL執行計劃。CURSOR_CHILD_NO爲子游標的序號,默認爲0,如果設定爲NULL,那麼所有該父遊標下所有的子游標的執行計劃都將返回。參數FORMAT指定要顯示哪些信息,常用的有:IOSTATS(I/O信息顯示)、ALLSTATS(I/O信息顯示+PGA信息)、ADVANCED(顯示所有統計信息)、IOSTATS LAST或ALLSTATS LAST(只顯示最後一次執行的統計信息)。默認值TYPICAL只能顯示一個普通的執行計劃,不能顯示出實際返回的行。
更加詳細用法以及參數說明參考官方文檔。
1.2、如何讀懂執行計劃
1.2.1、執行計劃閱讀順序
從一個例子說明:
示例:
SQL> SET AUTOTRACE TRACEONLY; -- 只顯示執行計劃,不顯示結果集
SQL> select * from scott.emp a,scott.emp b where a.empno=b.mgr;
已選擇13行。
sqlplus中顯示的執行計劃
----------------------------------------------------------
Plan hash value: 992080948
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 988 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN | | 13 | 988 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 14 | 532 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_EMP | 14 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 13 | 494 | 4 (25)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EMP | 13 | 494 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("A"."EMPNO"="B"."MGR")
filter("A"."EMPNO"="B"."MGR")
5 - filter("B"."MGR" IS NOT NULL)
統計信息
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
2091 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
13 rows processed
上面的執行結果顯示,在SQLPLUS裏面看到的執行計劃不是那麼直接。
我們一般按縮進長度來判斷,縮進最大的最先執行(最右邊的),如果有2行縮進一樣,那麼就先執行上面的。
借用崔華的口訣:先從最開頭一直連續往右看,直到看到最右邊並列的地方;對於不併列的,靠右的先執行;如果遇到並列的,就從上往下看,對於並列的部分,靠上的先執行。
如果使用toad這樣的圖形工具,就可以清楚的顯示執行的順序:
1.2.2、執行計劃指標解釋
- 執行計劃中字段解釋:
ID: 一個序號,但不是執行的先後順序。執行的先後根據縮進來判斷。
Operation: 當前操作的內容。
Rows: 當前操作的Cardinality,Oracle估計當前操作的返回結果集。
Cost(CPU):Oracle 計算出來的一個數值(代價),用於說明SQL執行的代價。
Time:Oracle 估計當前操作的時間。
- 謂詞說明:
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("A"."EMPNO"="B"."MGR")
filter("A"."EMPNO"="B"."MGR")
5 - filter("B"."MGR" IS NOT NULL)
Access: 表示這個謂詞條件的值將會影響數據的訪問路勁(表還是索引)。
Filter:表示謂詞條件的值不會影響數據的訪問路勁,只起過濾的作用。
在謂詞中主要注意access,要考慮謂詞的條件,使用的訪問路徑是否正確。
- 統計信息說明:
db block gets : 從buffer cache中讀取的block的數量
consistent gets: 從buffer cache中讀取的undo數據的block的數量
physical reads: 從磁盤讀取的block的數量
redo size: DML生成的redo的大小
sorts (memory) :在內存執行的排序量
sorts (disk) :在磁盤上執行的排序量
Physical Reads通常是我們最關心的,如果這個值很高,說明要從磁盤請求大量的數據到Buffer Cache裏,通常意味着系統裏存在大量全表掃描的SQL語句,這會影響到數據庫的性能,因此儘量避免語句做全表掃描,對於全表掃描的SQL語句,建議增 加相關的索引,優化SQL語句來解決。
關於physical reads ,db block gets 和consistent gets這三個參數之間有一個換算公式:
數據緩衝區的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。
用以下語句可以查看數據緩衝區的命中率:
SQL>SELECT name, value FROM v$sysstat WHERE name IN ('db block gets', 'consistent gets','physical reads');
查詢出來的結果Buffer Cache的命中率應該在90%以上,否則需要增加數據緩衝區的大小。
它們三者之間的關係大致可概括爲:
邏輯讀指的是Oracle從內存讀到的數據塊數量。一般來說是’consistent gets’ + ‘db block gets’。當在內存中找不到所需的數據塊的話就需要從磁盤中獲取,於是就產生了’physical reads’
2、穩固執行計劃
資料位置:E:\1@Repository IT\1.0@Oracle\1.0.2@性能優化\執行計劃&SQL plan\sql執行計劃穩固
How to Use SQL Plan Management (SPM) - Plan Stability Worked Example (Doc ID 456518.1)
參考:
2.0、把SQL執行計劃從shared pool刷出去
keep 執行計劃請參考:How to Pin a Cursor in the Shared Pool using DBMS_SHARED_POOL.KEEP (Doc ID 726780.1)
刷執行計劃父遊標如下:
select sql_text,sql_id,version_count,executions,OBJECT_STATUS,address,hash_value from v$sqlarea where sql_id='atv649gxpk8ga';
select PLAN_HASH_VALUE,q'[exec sys.dbms_shared_pool.purge(']'||address||','||hash_value||q'[','C');]' as flush_sql from v$sqlarea where sql_id='63u74y7gdafzf';
2.1、SQL Profile方式固定執行計劃
2.1.1、SQL Profile管理
SELECT task_name,status FROM DBA_ADVISOR_TASKS;
select * from dba_profiles;
禁用profile
begin
dbms_sqltune.alter_sql_profile(
name => 'SYS_SQLPROF_01534b8309b90000',
attribute_name => 'status',
value => 'disabled');
end;
/
啓用profile
begin
dbms_sqltune.alter_sql_profile(
name => 'SYS_SQLPROF_01534b8309b90000',
attribute_name => 'status',
value => 'enabled');
end;
/
刪除profile或解綁profile
BEGIN
DBMS_SQLTUNE.DROP_SQL_PROFILE(name => 'SYS_SQLPROF_01534b8309b90000');
END;
/
2.1.2、Automatic SQL Profile
通過sqltuning方式來創建SQL Profile;automatic類型的SQL profile其實就是針對目標SQL的一些額外調整信息,並且存儲在數據字典裏。Oracle在產生執行計劃時就根據它對目標SQL所涉及的統計信息等內容做調整,因此可以一定程度上避免錯誤的執行計劃。automatic類型的SQL profile本質上針對目標SQL的額外信息調整,所以原始SQL的統計信息等內容發生改變後該SQL的執行計劃也可能發生變化。所以automatic類型的SQL Profile並不能完全祈禱穩定目標SQL的執行計劃,雖然它確實可以用來調整執行計劃。
測試和創建過程如下:使用sql tuning advisor
1)創建表,插入數據,建立索引,收集統計信息
create table t1 (a number,b number,c varchar2(10));
declare
na integer;
nc varchar2(10) := 'test1';
a1 integer :=&minnum;
a2 integer :=&maxnum;
nb integer :=a2+1;
begin
for na in a1..a2
loop
insert into t1 (a, b, c) values (na,nb-na,nc);
end loop;
commit;
end;
/
create index t1_idx on t1(a);
begin dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'T1',method_opt=>'for all columns size 1',cascade => true);end;
/
2)執行SQL語句並且故意走錯執行計劃
select /*+ no_index(t1 t1_idx) */ * from t1 where a=100;
select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
3)執行SQL tuning任務
DECLARE
my_task_name VARCHAR2(30);
my_sqltext CLOB;
BEGIN
my_sqltext := 'select /*+ no_index(t1 t1_idx) */ * from t1 where a=100';
my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(sql_text => my_sqltext,
user_name => 'SYS',
scope => 'COMPREHENSIVE',
time_limit => 60,
task_name => 'tuning_sql_test',
description => 'Task to tune a query on a specified table');
DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => 'tuning_sql_test');
END;
/
4)查看sqltuning結果
SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK( 'tuning_sql_test') from DUAL;
5)接受sql profile
execute dbms_sqltune.accept_sql_profile(task_name => 'tuning_sql_test',task_owner => 'SYS', replace => TRUE);
2.1.3、Manual SQL Profile
Manual類型的SQL Profile本質上就是一堆Hint的組合,這一堆Hint的組合實際上來源於執行計劃中OutlineData部分的Hint組合。Manual類型的SQLProfile同樣可以在不更改SQL的SQL文本的情況下調整其執行計劃,而且相對於automatic類型的SQLprofile可以很好的穩定目標SQL的執行計劃。
創建Manual類型的SQL Profile可以藉助MOS上的文檔SQLT(SQLTXPLAIN)-Tool that helps to diagnose SQL statements performing poorly [ID 215187.1]中的腳本coe_xfr_sql_profile.sql。這個腳本適合從shared pool、awr repository中指定SQL的執行計劃的Outline Data部分的Hint組合,來創建Manul類型的SQL Profile。
2.1.3.1、場景1:shared pool中已經有比較好的執行計劃
執行執行coe_xfr_sql_profile.sql腳本,選取代價比較小的執行計劃即可。
2.1.3.2、場景2:沒有好的執行計劃可供選擇,需要手工構造比較優的執行計劃
1)針對目標SQL使用腳本coe_xfr_sql_profile.sql產生能生成其Manual類型的SQL Profile的腳本A。
2)改寫目標SQL的文本,在其中使用合適的Hint,直到加入Hint後SQL能走出想要的執行計劃。然後對加入合適Hint後的SQL使用腳本coe_xfr_sql_profile.sql,產生能生成其Manual類型的SQL Profile的腳本B。
3)使用腳本B中的OutlineData部分的Hint組合替換掉腳本A中的OutlineData部分的Hint組合。
4)執行修改後的腳本A生成針對原目標SQL的Manual類型SQL Profile.
方法1:手動的樂趣,使用B腳本中的類似如下部分內容去替換A腳本中部分:
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('11.1.0.7')]',
q'[DB_VERSION('11.1.0.7')]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
q'[END_OUTLINE_DATA]');
方法2: 通過import_sql_profile手動增加import_sql_profile
begin
dbms_sqltune.import_sql_profile
(name=>'sp_01',
sql_text=>'select object_name from t1 where object_id=10',
replace=>true,
profile=>sqlprof_attr('FULL(t1@SEL$1)')) ;
end;
/
進化版本:通過dblink方式獲取讀庫上sql文本
DECLARE
clsql_text varchar2(3000);
BEGIN
SELECT SQL_TEXT INTO clsql_text FROM v$sqlarea@R12201_TO_DG1 WHERE sql_id = '&SQL_ID';
DBMS_SQLTUNE.IMPORT_SQL_PROFILE( sql_text => clsql_text, profile => sqlprof_attr('USE_INVISIBLE_INDEXES'), name => 'PROFILE_&SQL_ID', force_match => TRUE );
END;
/
其中sqlprof_attr部分就是上面提到的替換部分的內容。sql profile中的hint信息都是存放在sys.sqlobj$data中的comp_data的欄位中,以xml格式存儲的也可以通過從這個欄位來獲取sqlprof_attr信息。
2.1.3.3、coe_xfr_sql_profile.sql改良版腳本
來自Oracle內部員工的“真香”腳本。
腳本:https://github.com/AlbertCQY/scripts/tree/master/oracle 目錄下sql_profile_new2.sql腳本
腳本用法:
@sql_profile_new2.sql
Parameter 1:
SQL_ID (required)
Enter value for 1: --這裏傳入需要優化的sqlid
Parameter 2:
PLAN_HASH_VALUE (required)
Enter value for 2: --這裏傳入正確執行計劃的PLAN_HASH_VALUE,可以不是Parameter 1對應sqlid的plan_hash
最後一步是執行生成的sql腳本。
2.2、SPM&SQL Baseline&基線
2.2.1、SQL Plan Baseline基本特點
1)通過OPTIMIZER_USE_SQL_PLAN_BASELINE來控制Oracle是否使用基線,默認值爲TRUE,即會自動使用基線。
2)11g中默認是不會自動創建基線
3)與OUTLINE和SQL Profile不同,基線中不存在分類的概念
4)與OUTLINE和SQL Profile不同,每個SQL語句可以有多個基線。Oracle根據制定的規則來判斷具體是否哪個基線
5)基線針對RAC中所有的實例都生效
6)基線有兩個標示,一個爲sql_handle,可以理解爲表示語句文本的唯一標識,一個爲sql_plan_name可以理解爲執行計劃的唯一標識
7)不能像sql profile一樣通過force_matching屬性將字面值不一樣的SQL語句使用一個基線應用多個語句。
2.2.2、創建基線的幾種方式
- 自動捕獲基線,通過將optimizer_capture_sql_plan_baselines設置爲true(默認值爲false),優化器爲重複執行兩次以上的SQL語句生成並保存基線(可以系統級或會話級修改)
alter session set optimizer_capture_sql_plan_baselines = true;
或
alter system set optimizer_capture_sql_plan_baselines = true;
- 手動load
如下圖所示,有4中途徑:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RIJp3Nhx-1574082782220)(686D090B38BA40B3A4C07FE27E6165AA)]
1、從 SQL Tuning Set STS 導DBMS_SPM.LOAD_PLANS_FROM_SQLSET;
2、從Stored Outlines 中導入DBMS_SPM.MIGRATE_STORED_OUTLINE;
3、從內存中存在的計劃中導入DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE;
4、通過staging table從另外一個系統中移植
DBMS_SPM.CREATE_STGTAB_BASELINE
DBMS_SPM.PACK_STGTAB_BASELINE
DBMS_SPM.UNPACK_STGTAB_BASELINE
(注意:這些導入的baseline都會被自動標記爲ACCEPTED)
最常用的方法:
- 從庫緩存中加載,通過包dbms_spm.load_plans_from_cursor_cache函數爲一條已經在遊標緩存中的語句創建基線
DECLARE
l_plans_loaded PLS_INTEGER;
BEGIN
l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '1fkh93md0802n',plan_hash_value=>null);
END;
/
- 從SQL調優集合中加載,通過使用包dbms_spm.load_plans_from_sqlset來從SQL調優集合中加載基線
DECLARE
l_plans_loaded PLS_INTEGER;
BEGIN
l_plans_loaded := DBMS_SPM.load_plans_from_sqlset(
sqlset_name => 'my_sqlset');
END;
/
2.2.3、基線的幾種狀態
一個SQL語句對應的基線,我將它們歸納爲三種狀態
accepted(可接受),只有這種狀態的基線,優化器纔會考慮此基線中的執行計劃
no-accepted(不可接受),這種狀態的基線,優化器在SQL語句解析期間不會考慮。這種狀態的基線必須通過演化和驗證通過後,轉變爲accepted狀態後,纔會被優化器考慮使用
fixed爲yes(固定),這種狀態的基線固有最高優先級!比其他兩類基線都要優先考慮
2.2.4、查看基線
-
基本視圖:dba_sql_plan_baselines、dba_sql_management_config
-
底層視圖:sqlobj (保存具體的hint),如下查看基線中保存的執行計劃hint集合語句:
select extractvalue(value(d), '/hint') as outline_hints from
xmltable('/outline_data/hint'
passing (select xmltype(comp_data) as xmlval from
sqlobj$data sod, sqlobj$ so where so.signature = sod.signature
and so.plan_id = sod.plan_id
and comp_data is not null
and name like '&baseline_plan_name')) d;
- 通過函數來查看基線的詳細信息:
select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle=>'SYS_SQL_11bcd50cd51504e9',plan_name=>'SQL_PLAN_13g6p1maja1790cce5f0e'));
2.2.5、演化基線
爲了驗證基線中一個處於不可接受狀態的執行計劃是否比一個處於可接受狀態的執行計劃具有更高的效率,必須通過演化來驗證,需要讓優化器以不同的執行計劃來執行這條SQL語句,觀察不可接受狀態的執行計劃基線是否會帶來更好的性能,如果性能確實更高,這個不可接受狀態的基線將會轉換爲可接受狀態。演化的方式有兩種:
1)手工執行運行
SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_xxxxxxxxxxxxx') From dual;
還有time_limit/verify/commit幾個參數,可以參考文檔
- 調優包實現基線的自動演化,可以理解爲,啓動一個調度任務,週期性的檢查是否有不可接受狀態的基線可以被演化
2.2.6、修改基線
可以通過dbms_spm.alter_sql_plan_baseline包來修改基線的一些屬性,主要有如下幾個屬性
- ENABLED :設置該屬性的值爲NO告訴Oracle 11g臨時禁用某個計劃,一個SQL計劃必須同時標記爲ENABLED和ACCEPTED,否則CBO將忽略它
- FIXED:設置爲YES,那個計劃將是優化器唯一的選擇[最高優先級],即使如果某個計劃可能擁有更低的成本。這讓DBA可以撤銷SMB的默認行爲,對於轉換一個存儲概要進入一穩定的SQL計劃基線特別有用,注意當一個新計劃被添加到被標記爲FIXED的SQL計劃基線,該新計劃不能被利用除非它申明爲FIXED狀態
- AUTOPURG:設置這個屬性的值爲NO告訴Oracle 11g無限期保留它,從而不用擔心SMB的自動清除機制
- plan_name : 改變SQL plan 名字
- description : 改變SQL plan描述
語法:
SET SERVEROUTPUT ON
DECLARE
v_text PLS_INTEGER;
BEGIN
v_text := DBMS_SPM.alter_sql_plan_baseline(sql_handle => 'SYS_SQL_xxxxxx',plan_name => 'SYS_SQL_PLAN_xxxxxxxxx',
attribute_name => 'fixed',attribute_value => 'YES');
DBMS_OUTPUT.put_line('Plans Altered: ' || v_text );
END;
/
2.2.7、刪除基線
- 可以通過dbms_SPM.drop_sql_plan_baseline包來手工刪除數據字典裏的基線
- 爲使用的基線,fixed爲no的基線,將在一定的保留期後自動刪除(可查看dba_sql_management_config視圖)
手工刪除方法如下:
SET SERVEROUTPUT ON
DECLARE
v_text PLS_INTEGER;
BEGIN
v_text := DBMS_SPM.drop_sql_plan_baseline(sql_handle => 'SYS_SQL_7b76323ad90440b9',plan_name => NULL);
DBMS_OUTPUT.put_line(v_text);
END;
/
2.2.8、遷移基線
dbms_spm提供了多個過程來在數據庫之間遷移SQL計劃基線
- create_stgtab_baseline創建一個計劃基線保存表
- pack_stgtab_baseline將基線從數據字典複製到第一步的表中
- unpack_stgtab_baseline將基線從保存表中複製到遷移數據庫的數據字典中
大概過程如下: - 創建一張保存數據字典中基線表內容的用戶表
exec dbms_spm.create_stgtab_baseline(table_name => 'BASELINE_TEST',table_owner => 'SCOTT',tablespace_name =>'');
- 將數據字典中基線表的內容 插入到 第一步創建的用戶表中
exec :i := dbms_spm.pack_stgtab_baseline(table_name => 'BASELINE_TEST', table_owner => 'SCOTT');
備註:可以支持多種方式插入,例如包含特定字符的SQL相關的基線,sql_handle來精確識別一個基線,具體見文檔
3) 通過遷移工具遷移用戶表
exp/imp or expdp/impdp
4) 將遷移過來的用戶表中保存的基線內容 插入到當前庫的數據字典中,從而實現遷移
exec :i := dbms_spm.unpack_stgtab_baseline(table_name => 'BASELINE_TEST',table_owner => 'SCOTT');
2.3、SPM方式固定執行計劃
2.3.1、已經有好的執行計劃
手工load比較優的執行計劃即可:
DECLARE
l_plans_loaded PLS_INTEGER;
BEGIN
l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '1fkh93md0802n',plan_hash_value=>3708376029);
END;
/
2.3.2、需要手工構造好的執行計劃
這種情況一般是當前sql執行計劃差,但是又不能修改sql文本。還是使用前面講到的偷樑換柱思想,SPM方式處理起來相對來說比較簡單。
假如說我已經把需要調優sql的執行計劃load進去了,記錄下sql_handle。但是執行計劃不是我想要的。這時候我可以用hint構造一個理想的執行計劃。然後加sql_handle參數把好的執行計劃的sql_id和plan_hash_value加載進去。sql_handle還是原來的。
這時候可以看到同一個sql_handle下有兩條執行計劃,把原來的刪除即可。
操作步驟如下:
- 加載目標SQL的執行計劃
DECLARE
k1 pls_integer;
begin
k1 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
sql_id=>'4vaj9fgjysy9c', --目標SQL_ID
plan_hash_value=>1845196118); --目標SQL_planhash
end;
/
- 查詢出剛纔加載的目標SQL的sql_handle
select * from dba_sql_plan_baselines where ...
- hint 提示方式構造一個比較優的執行計劃
這裏記錄下構造後sql的sql_id和sql plan hash - 進行替換
DECLARE
k2 pls_integer;
begin
k2 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
sql_id=>'fm35jcmypb3qu', --構造的sql_id
plan_hash_value=>2780970545, --構造sql的plan hash
sql_handle=>'SYS_SQL_11bcd50cd51504e9' ); --目標sql加載後的sql_handle
end;
/
- 刪除第一步中加載的,不需要的執行計劃
DECLARE
k1 pls_integer;
begin
k1 := DBMS_SPM.drop_sql_plan_baseline (
sql_handle=>'SYS_SQL_11bcd50cd51504e9',
plan_name=>'SQL_PLAN_13g6p1maja17934f41c8d'); --這裏是同一個sql_handle下的錯誤執行計劃的plan_name,即第一步生成的
end;
/
測試記錄:
目的:讓執行計劃走上全表掃描
查詢語句:select count(*) from wxh_tbd where object_id=:a
SQL_ID:85f05qy1aq0dr
PLAN_HASH_VALUE:1501268522
步驟一-------------------------創建測試表,根據DBA_OBJECTS創建,OBJECT_ID上有索引
Create table wxh_tbd as select * from dba_objects;
create index t_3 on wxh_tbd(object_id);
步驟二------------------------創建指定SQLID的BASELINE,後面要做修改,由於默認走的索引
declare
l_pls number;
begin
l_pls := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id => '85f05qy1aq0dr',
plan_hash_value => 1501268522,
enabled => 'NO');
end;
/
步驟三--------------------------想辦法構造出執行計劃爲全表掃描的SQL_ID
var a number
exec :a :=1234
select /*+ full(wxh_tbd) */count(*) from wxh_tbd where object_id=:a;
select sql_id ,sql_text from v$sql where sql_text like '%wxh_tbd%';
查出SQL_ID爲89143jku5hzcw,PLAN_HASH_VALUE爲853361775
步驟四--------------------------確定原始執行計劃的 sql_handle
select sql_handle, plan_name, origin, enabled, accepted,fixed,optimizer_cost,sql_text
from dba_sql_plan_baselines where sql_text like '%count(*) from wxh_tbd %'
order by last_modified;
SQL_HANDLE:SYS_SQL_ad9f0ff741832bd9
PLAN_NAME:SYS_SQL_PLAN_41832bd9d63f8aa9
步驟五------------------------與正確的執行計劃做關聯
declare l_pls number;
begin
l_pls := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '89143jku5hzcw', -- hinted_SQL_ID'
plan_hash_value => 853361775, --hinted_plan_hash_value
sql_handle => 'SYS_SQL_ad9f0ff741832bd9' --sql_handle_for_original
);
end;
/
步驟六--------------------------刪除錯誤的執行計劃
declare l_pls number;
begin
l_pls := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => 'SYS_SQL_ad9f0ff741832bd9', --sql_handle_for_original
plan_name => 'SYS_SQL_PLAN_41832bd9d63f8aa9 ' --sql_plan_name_for_original
);
end;
/
步驟七----------------------確認是否使用到BASELINE
explain plan for select count(*) from wxh_tbd where object_id=:a;
select * from table(dbms_xplan.display);
--------------------------------------
| Id | Operation | Name |
--------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
|* 2 | TABLE ACCESS FULL| WXH_TBD |
--------------------------------------
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_41832bd9cca3d082" used for this statement