轉 oracle 監控執行計划突然變化


 ########sample 執行計划突然變化

問題:

 接受到一條信息,執行計划突然變化了。

SELECT /*+ db120190621 no_expand */ INTERNAL_KEY FROM AA.RB_bb WHERE BASE_bb_NO = :B4 AND CCY = NVL (:B3 , CCY) AND bb_TYPE = NVL (:B2 , bb_TYPE) AND NVL (CERTIFICATE_NO, '~') = NVL (:B1 , '~')

 

快的:

"BASE_bb_NO"      : Field {  type=FieldType[string]  length=50  scale=0  value= {0017091458012} }

"CCY"       : Field {  type=FieldType[string]  length=3  scale=0  value= {dd} }

    "bb_TYPE" : Field {  type=FieldType[string]  length=6  scale=0  value= {210008} }

 

慢的:

"BASE_bb_NO"      : Field {  type=FieldType[string]  length=50  scale=0  value= {512100008627000003} }

    "CCY"       : Field {  type=FieldType[string]  length=3  scale=0  value= {dd} }

    "bb_TYPE" : Field {  type=FieldType[string]  length=6  scale=0  value= {110002} }

 

CERTIFICATE_NO都是空值。

 

Inst: 1   Child: 0    Plan hash value: 2601219506

 

                      ------------------------------------------------------------------------------------------

                      | Id  | Operation                     | Name    | E-Rows |E-Bytes| Cost (%CPU)| E-Time   |

                      ------------------------------------------------------------------------------------------

                      |   0 | SELECT STATEMENT              |         |        |       |     7 (100)|          |

                      |   1 |  CONCATENATION                |         |        |       |            |          |

                      |*  2 |   FILTER                      |         |        |       |            |          |

                      |*  3 |    TABLE ACCESS BY INDEX ROWID| RB_bb |      1 |    40 |     5   (0)| 00:00:01 |

                      |*  4 |     INDEX RANGE SCAN          | RAC_I5  |      1 |       |     3   (0)| 00:00:01 |

                      |*  5 |   FILTER                      |         |        |       |            |          |

                      |*  6 |    TABLE ACCESS BY INDEX ROWID| RB_bb |      1 |    40 |     2   (0)| 00:00:01 |

                      |*  7 |     INDEX RANGE SCAN          | RAC_I1  |      1 |       |     2   (0)| 00:00:01 |

                      ------------------------------------------------------------------------------------------

 

 

現象:

->1.重現問題SQL,因為使使用綁定變量,所以使用綁定變量重現問題執行計划,同時使用hint 生成 硬解析 ##add hint /* */ 是為了重新硬解析 重要

另外,綁定變量值為空值的時候,也需要使用null值替代。這樣在每次硬解析的時候,我們可以發現確實執行計划如應用描述一樣,選擇了2條不一樣的執行計划。

綁定變量重現方法,如下:

 

sql1:RB_bb 97vurxcnctd16

 

var B1 varchar2(32);
var B2 VARCHAR2(32);
var B3 VARCHAR2(32);
var B4 VARCHAR2(32);
var B5 VARCHAR2(32);
exec :B1:=null;
exec :B2:='110002';
exec :B3:='dd';
exec :B4:='0000204742011';

SELECT /*+ db120190622 */ INTERNAL_KEY FROM AA.RB_bb WHERE BASE_bb_NO = :B4 AND CCY = :B3 AND bb_TYPE = NVL (:B2 , bb_TYPE) AND NVL (CERTIFICATE_NO, '~') =
NVL (:B1 , NVL(CERTIFICATE_NO, '~'))

select * from table(dbms_xplan.display_cursor());

 

-> 2.1同時查閱資料,發現這是條簡單的單表查詢,但在執行計划里使用了2次 index 范圍查詢。

第一次是

                      |*  4 |     INDEX RANGE SCAN          | RAC_I5  |      1 |       |     3   (0)| 00:00:01 |

 第二次是

                      |*  7 |     INDEX RANGE SCAN          | RAC_I1  |      1 |       |     2   (0)| 00:00:01 |

 

檢查原因,發現是11g 在執行綁定變量的函數查詢時候,比如 NVL (:B2 , bb_TYPE) 查詢,因為無法判斷綁定變量是否是空值,所以會執行2次索引掃描查詢,並將結果組合起來,返回結果,如下所示,當B2為null,執行一次索引掃描,B2 不為null,執行一次索引掃描,並將結果組合起來。

 

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

                         2 - filter(:B2 IS NULL)
                         3 - filter((NVL("CERTIFICATE_NO",'~')=NVL(:B1,'~') AND "CCY"=NVL(:B3,"CCY")
                                    AND "bb_TYPE" IS NOT NULL))
                         4 - access("BASE_bb_NO"=:B4)
                         5 - filter(:B2 IS NOT NULL)
                         6 - filter(("BASE_bb_NO"=:B4 AND NVL("CERTIFICATE_NO",'~')=NVL(:B1,'~') AND
                                    "CCY"=NVL(:B3,"CCY")))
                         7 - access("bb_TYPE"=:B2)

 

->2.2 為了避免使用2次索引掃描,可以使用如下方法 no_expand  hint  來避免使用CONCATENATION  查詢。

(https://www.cnblogs.com/wait4friend/archive/2012/12/14/2818164.html)

SELECT /*+ no_expand */

       INTERNAL_KEY

  FROM AA.RB_bb

WHERE     BASE_bb_NO = '516100292728100001'

       AND CCY = NVL ('dd', CCY)

       AND bb_TYPE = NVL ('110002', bb_TYPE)

       AND NVL (CERTIFICATE_NO, '~') = NVL ('', '~')

 

執行計划應該如下:

Plan hash value: 1947998975

 

---------------------------------------------------------------------------------------

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |         |     1 |    40 |     4   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS BY INDEX ROWID| RB_bb |     1 |    40 |     4   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | RAC_I18 |     1 |       |     3   (0)| 00:00:01 |

---------------------------------------------------------------------------------------

 

Predicate Information (identified by operation id):

---------------------------------------------------

 

   1 - filter(NVL("CERTIFICATE_NO",'~')='~')

   2 - access("BASE_bb_NO"='516100292728100001' AND "bb_TYPE"='110002' AND

              "CCY"='dd')

 

 

3.該執行計划變化原因暫時也沒查明:

 

理論上硬解析應該不會出現在數據庫上,如果出現,有可能原因是數據庫SQL可能運行在不同的節點上了。還有就是對象存在DDL操作。

 


Query Using Bind Variables Suddenly Starts to Perform Slowly ( Doc ID 387394.1 )

"When bind variables are used in a statement, it is assumed that cursor sharing is intended and that different invocations are supposed to use the same execution plan. If different invocations of the cursor would significantly benefit from different execution plans, then bind variables may have been used inappropriately in the SQL statement."

Additionally, bind peeking has been known to cause a different execution plan to be used on different nodes of a RAC cluster because each node has its own Shared Pool. Despite the same SQL, data, and statistics, the first time a cursor was hard parsed on each node a different set of bind values was presented to the optimizer, choosing a different plan on each node.

 

It is expected behavior that execution plan changes since the bind variables are different.
If want to use one specific execution plan, please fix the execution plan.

SQL> var nRet NUMBER
SQL> EXEC :nRet := dbms_spm.load_plans_from_cursor_cache(sql_id=>'<your sql_id> ',plan_hash_value=><your paln_id>,fixed=>'YES',enabled=>'YES');


可能原因:
總結來說,綁定變量窺探會於第一次硬解析的時候,“窺探“綁定變量的值,進而根據該值的信息,輔助選擇更加准確的執行計划,就像上述示例中第一次執行A為條件的SQL,
知道A值占比重接近全表數據量,因此選擇了全表掃描。但若綁定變量列分布不均勻,則綁定變量窺探的副作用會很明顯,第二次以后的每次執行,無論綁定變量列值是什么,
都會僅使用第一次硬解析窺探的參數值,這就有可能選擇錯誤的執行計划,就像上面這個實驗中說明的,第二次使用B為條件的SQL,除非再次硬解析,否則這種情況不會改變。

 

 

 

規避方法:

1.1使用no_expand hint 的SQL 生成一個優化的執行計划,在替代掉生產有問題的SQL

 

 


->執行方法,強制sql 使用118 號索引

0.
刪除之前的profile
EXEC DBMS_SQLTUNE.DROP_SQL_PROFILE(Name => 'coe_cdquh2m4d3gjm_4076902948');

1. 構造一個好的執行計划:


var B1 varchar2(32);
var B2 VARCHAR2(32);
var B3 VARCHAR2(32);
exec :B1:='120008';
exec :B2:='dd';
exec :B3:='0000204742011';

SELECT /*+ db120191110 no_expand */ INTERNAL_KEY FROM user.tab WHERE BASE_ACCT_NO = :B3 AND CCY = :B2 AND ACCT_TYPE = NVL (:B1 , ACCT_TYPE)


select * from table(dbms_xplan.display_cursor());

 

 


2.1、抓取該SQL信息,並捕獲該SQL的執行信息文件

select sql_id,plan_hash_value,sql_text from v$sql where sql_text like '%/*+ db120191110 no_expand */%';


40bkjwtyvuzmu 1947998975

 

2.2、Run the script coe_xfr_sql_profile.sql as SYSDBA the sql_id and the good Plan Hash Value

START coe_xfr_sql_profile.sql <sql_id> <plan hash value for good plan>

@/db/db1/app/db/product/11204/rdbms/admin/coe_xfr_sql_profile.sql 40bkjwtyvuzmu 1947998975


2.3 修改該執行文件信息

->SQL_內容去掉 hint ,同時去掉 表名前的 用戶名

wa(q'[SELECT /*+ test */TABLE_NAME, TABLE_OWNER, DB_LINK FROM ALL_SYNO]');

wa(q'[NYMS WHERE OWNER = 'PUBLIC' AND SYNONYM_NAME = 'test' ]');

==============================================================》

wa(q'[SELECT /*+RULE*/ TABLE_NAME, TABLE_OWNER, DB_LINK FROM ALL_SYNO]');

wa(q'[NYMS WHERE OWNER = 'PUBLIC' AND SYNONYM_NAME = 'test' ]');

 


->force_match 改成true
force_match => FALSE

====================》

 

force_match => TRUE

 

 

5、在數據庫中執行綁定腳本

 

@coe_xfr_sql_profile_bpwkfmp2yzmdd_244914142.sql


5.2
select name,to_char(sql_text) from dba_sql_profiles
coe_faa81zfq2ws27_2769088497

6、測試語句是否使用此sqlprofile


--user user/userqaz
set autot on

var B1 varchar2(32);
var B2 VARCHAR2(32);
var B3 VARCHAR2(32);
exec :B1:='120008';
exec :B2:='dd';
exec :B3:='0000204742011';

SELECT INTERNAL_KEY FROM tabWHERE BASE_ACCT_NO = :B3 AND CCY = :B2 AND ACCT_TYPE = NVL (:B1 , ACCT_TYPE)

 

Note

-----

- SQL profile "coe_bpwkfmp2yzmdd_244914142" used for this statement-----說明綁定成功

 

 

 

 

2,開啟數據庫監控,監控執行計划變化的SQL,提前獲取可能潛在的問題。

version 2: 最近一天的排在top 15位的 在最近3天有執行計划變化的SQL
select count(*),text
from (SELECT
CHR(10) || 'sql_id -> ' || sql_id text
FROM (SELECT /*+ NO_MERGE */
h.sql_id,
h.plan_hash_value,
MIN(s.end_interval_time) first_snap_time,
MAX(s.end_interval_time) last_snap_time
FROM dba_hist_sqlstat h, dba_hist_snapshot s
WHERE h.sql_id in
(select sql_id
from (SELECT h.dbid,
h.sql_id,
TO_CHAR(SUM(h.elapsed_time_total),
'999999999999999990D990') elapsed
FROM dba_hist_sqlstat h, dba_hist_snapshot s
WHERE h.executions_total > 0
AND s.snap_id = h.snap_id
AND s.dbid = h.dbid
AND s.instance_number = h.instance_number
AND CAST(s.end_interval_time AS DATE) >
SYSDATE - 1
GROUP BY h.dbid, h.sql_id
order by elapsed desc)
where ROWNUM <= 15)
AND h.snap_id = s.snap_id
AND h.dbid = s.dbid
and s.end_interval_time > sysdate - 3
AND h.instance_number = s.instance_number
GROUP BY h.plan_hash_value, h.sql_id
ORDER BY 2) v )
group by text
having count(*) > 1

 

##############1.

https://carlos-sierra.net/2014/11/02/finding-sql-with-performance-changing-over-time/

該sql 太復雜,執行時間太長,但是是很好參考資料。

Finding SQL with Performance changing over time

with 10 comments

I upgraded my database a couple of weeks ago and now my users complain their application is slower. They do not provide specifics but they “feel” it is running slower. Sounds familiar?

Every once in a while I get a request that goes like this: “how can I find if some SQL on my database is performing worse over time?”

It is very hard to deal with the ambiguities of some problems like “finding SQL that performs worse or better over time”. But if you simplify the problem and consider for example “Elapsed Time per Execution”, then you can easily produce a script like the one below, which returns a small list of SQL statements that seem to experience either a regression or an improvement over time. It uses linear regression on the ratio between “Elapsed Time per Execution” and its Median per SQL.

Then, If you are suspecting you have some SQL that may have regressed and need a hand to identify them, you can try this script below. It is now part of a small collection of scripts that you can download and use for free out of the cscripts link on the right hand side of this page, under “Downloads”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
----------------------------------------------------------------------------------------
--
-- File name:   sql_performance_changed.sql
--
-- Purpose:     Lists SQL Statements with Elapsed Time per Execution changing over time
--
-- Author:      Carlos Sierra
--
-- Version:     2014/10/31
--
-- Usage:       Lists statements that have changed their elapsed time per execution over
--              some history.
--              Uses the ration between "elapsed time per execution" and the median of
--              this metric for SQL statements within the sampled history, and using
--              linear regression identifies those that have changed the most. In other
--              words where the slope of the linear regression is larger. Positive slopes
--              are considered "improving" while negative are "regressing".
--
-- Example:     @sql_performance_changed.sql
--
-- Notes:       Developed and tested on 11.2.0.3.
--
--              Requires an Oracle Diagnostics Pack License since AWR data is accessed.
--
--              To further investigate poorly performing SQL use sqltxplain.sql or sqlhc
--              (or planx.sql or sqlmon.sql or sqlash.sql).
--            
---------------------------------------------------------------------------------------
--
SPO sql_performance_changed.txt;
DEF days_of_history_accessed = '31' ;
DEF captured_at_least_x_times = '10' ;
DEF captured_at_least_x_days_apart = '5' ;
DEF med_elap_microsecs_threshold = '1e4' ;
DEF min_slope_threshold = '0.1' ;
DEF max_num_rows = '20' ;
 
SET lin 200 ver OFF ;
COL row_n FOR A2 HEA '#' ;
COL med_secs_per_exec HEA 'Median Secs|Per Exec' ;
COL std_secs_per_exec HEA 'Std Dev Secs|Per Exec' ;
COL avg_secs_per_exec HEA 'Avg Secs|Per Exec' ;
COL min_secs_per_exec HEA 'Min Secs|Per Exec' ;
COL max_secs_per_exec HEA 'Max Secs|Per Exec' ;
COL plans FOR 9999;
COL sql_text_80 FOR A80;
 
PRO SQL Statements with "Elapsed Time per Execution" changing over time
 
WITH
per_time AS (
SELECT h.dbid,
        h.sql_id,
        SYSDATE - CAST (s.end_interval_time AS DATE ) days_ago,
        SUM (h.elapsed_time_total) / SUM (h.executions_total) time_per_exec
   FROM dba_hist_sqlstat h,
        dba_hist_snapshot s
  WHERE h.executions_total > 0
    AND s.snap_id = h.snap_id
    AND s.dbid = h.dbid
    AND s.instance_number = h.instance_number
    AND CAST (s.end_interval_time AS DATE ) > SYSDATE - &&days_of_history_accessed.
  GROUP BY
        h.dbid,
        h.sql_id,
        SYSDATE - CAST (s.end_interval_time AS DATE )
),
avg_time AS (
SELECT dbid,
        sql_id,
        MEDIAN(time_per_exec) med_time_per_exec,
        STDDEV(time_per_exec) std_time_per_exec,
        AVG (time_per_exec)    avg_time_per_exec,
        MIN (time_per_exec)    min_time_per_exec,
        MAX (time_per_exec)    max_time_per_exec      
   FROM per_time
  GROUP BY
        dbid,
        sql_id
HAVING COUNT (*) >= &&captured_at_least_x_times.
    AND MAX (days_ago) - MIN (days_ago) >= &&captured_at_least_x_days_apart.
    AND MEDIAN(time_per_exec) > &&med_elap_microsecs_threshold.
),
time_over_median AS (
SELECT h.dbid,
        h.sql_id,
        h.days_ago,
        (h.time_per_exec / a.med_time_per_exec) time_per_exec_over_med,
        a.med_time_per_exec,
        a.std_time_per_exec,
        a.avg_time_per_exec,
        a.min_time_per_exec,
        a.max_time_per_exec
   FROM per_time h, avg_time a
  WHERE a.sql_id = h.sql_id
),
ranked AS (
SELECT RANK () OVER ( ORDER BY ABS (REGR_SLOPE(t.time_per_exec_over_med, t.days_ago)) DESC ) rank_num,
        t.dbid,
        t.sql_id,
        CASE WHEN REGR_SLOPE(t.time_per_exec_over_med, t.days_ago) > 0 THEN 'IMPROVING' ELSE 'REGRESSING' END change,
        ROUND(REGR_SLOPE(t.time_per_exec_over_med, t.days_ago), 3) slope,
        ROUND( AVG (t.med_time_per_exec)/1e6, 3) med_secs_per_exec,
        ROUND( AVG (t.std_time_per_exec)/1e6, 3) std_secs_per_exec,
        ROUND( AVG (t.avg_time_per_exec)/1e6, 3) avg_secs_per_exec,
        ROUND( MIN (t.min_time_per_exec)/1e6, 3) min_secs_per_exec,
        ROUND( MAX (t.max_time_per_exec)/1e6, 3) max_secs_per_exec
   FROM time_over_median t
  GROUP BY
        t.dbid,
        t.sql_id
HAVING ABS (REGR_SLOPE(t.time_per_exec_over_med, t.days_ago)) > &&min_slope_threshold.
)
SELECT LPAD(ROWNUM, 2) row_n,
        r.sql_id,
        r.change,
        TO_CHAR(r.slope, '990.000MI' ) slope,
        TO_CHAR(r.med_secs_per_exec, '999,990.000' ) med_secs_per_exec,
        TO_CHAR(r.std_secs_per_exec, '999,990.000' ) std_secs_per_exec,
        TO_CHAR(r.avg_secs_per_exec, '999,990.000' ) avg_secs_per_exec,
        TO_CHAR(r.min_secs_per_exec, '999,990.000' ) min_secs_per_exec,
        TO_CHAR(r.max_secs_per_exec, '999,990.000' ) max_secs_per_exec,
        ( SELECT COUNT ( DISTINCT p.plan_hash_value) FROM dba_hist_sql_plan p WHERE p.dbid = r.dbid AND p.sql_id = r.sql_id) plans,
        REPLACE (( SELECT DBMS_LOB.SUBSTR(s.sql_text, 80) FROM dba_hist_sqltext s WHERE s.dbid = r.dbid AND s.sql_id = r.sql_id), CHR(10)) sql_text_80
   FROM ranked r
  WHERE r.rank_num <= &&max_num_rows.
  ORDER BY
        r.rank_num
/
 
SPO OFF ;

Once you get the output of this script above, you can use the one below to actually list the time series for one of the SQL statements of interest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
----------------------------------------------------------------------------------------
--
-- File name:   one_sql_time_series.sql
--
-- Purpose:     Performance History for one SQL
--
-- Author:      Carlos Sierra
--
-- Version:     2014/10/31
--
-- Usage:       Script sql_performance_changed.sql lists SQL Statements with performance
--              improvement or regressed over some History.
--              This script one_sql_time_series.sql lists the Performance Time Series for
--              one SQL.
--
-- Parameters:  SQL_ID
--
-- Example:     @one_sql_time_series.sql
--
-- Notes:       Developed and tested on 11.2.0.3.
--
--              Requires an Oracle Diagnostics Pack License since AWR data is accessed.
--
--              To further investigate poorly performing SQL use sqltxplain.sql or sqlhc
--              (or planx.sql or sqlmon.sql or sqlash.sql).
--            
---------------------------------------------------------------------------------------
--
SPO one_sql_time_series.txt;
SET lin 200 ver OFF ;
 
COL instance_number FOR 9999 HEA 'Inst' ;
COL end_time HEA 'End Time' ;
COL plan_hash_value HEA 'Plan|Hash Value' ;
COL executions_total FOR 999,999 HEA 'Execs|Total' ;
COL rows_per_exec HEA 'Rows Per Exec' ;
COL et_secs_per_exec HEA 'Elap Secs|Per Exec' ;
COL cpu_secs_per_exec HEA 'CPU Secs|Per Exec' ;
COL io_secs_per_exec HEA 'IO Secs|Per Exec' ;
COL cl_secs_per_exec HEA 'Clus Secs|Per Exec' ;
COL ap_secs_per_exec HEA 'App Secs|Per Exec' ;
COL cc_secs_per_exec HEA 'Conc Secs|Per Exec' ;
COL pl_secs_per_exec HEA 'PLSQL Secs|Per Exec' ;
COL ja_secs_per_exec HEA 'Java Secs|Per Exec' ;
 
SELECT h.instance_number,
        TO_CHAR( CAST (s.end_interval_time AS DATE ), 'YYYY-MM-DD HH24:MI' ) end_time,
        h.plan_hash_value,
        h.executions_total,
        TO_CHAR(ROUND(h.rows_processed_total / h.executions_total), '999,999,999,999' ) rows_per_exec,
        TO_CHAR(ROUND(h.elapsed_time_total / h.executions_total / 1e6, 3), '999,990.000' ) et_secs_per_exec,
        TO_CHAR(ROUND(h.cpu_time_total / h.executions_total / 1e6, 3), '999,990.000' ) cpu_secs_per_exec,
        TO_CHAR(ROUND(h.iowait_total / h.executions_total / 1e6, 3), '999,990.000' ) io_secs_per_exec,
        TO_CHAR(ROUND(h.clwait_total / h.executions_total / 1e6, 3), '999,990.000' ) cl_secs_per_exec,
        TO_CHAR(ROUND(h.apwait_total / h.executions_total / 1e6, 3), '999,990.000' ) ap_secs_per_exec,
        TO_CHAR(ROUND(h.ccwait_total / h.executions_total / 1e6, 3), '999,990.000' ) cc_secs_per_exec,
        TO_CHAR(ROUND(h.plsexec_time_total / h.executions_total / 1e6, 3), '999,990.000' ) pl_secs_per_exec,
        TO_CHAR(ROUND(h.javexec_time_total / h.executions_total / 1e6, 3), '999,990.000' ) ja_secs_per_exec
   FROM dba_hist_sqlstat h,
        dba_hist_snapshot s
  WHERE h.sql_id = '&sql_id.'
    AND h.executions_total > 0
    AND s.snap_id = h.snap_id
    AND s.dbid = h.dbid
    AND s.instance_number = h.instance_number
  ORDER BY
        h.sql_id,
        h.instance_number,
        s.end_interval_time,
        h.plan_hash_value
/
 
SPO OFF ;
 
 
########2
 
https://www.cnblogs.com/pangblog/p/3268586.html
1、問題
       通過調用dbms_xplan包中DISPLAY_AWR函數(DBMS_XPLAN.DISPLAY_AWR)可以從AWR數據中查看到SQL語句的歷史執行計划,但是,DISPLAY_AWR函數的可傳入參數只有四種,分別為:sql_id、plan_hash_value、db_id、format,缺少與時間范圍相關的參數、也沒有instance_number相關參數。
       使用dbms_xplan.display_awr的簡單方式,一般為:
         SQL>select * from table(dbms_xplan.display_awr(db_id=> 19948XXXX2,sql_id=> 'bj75p9188y410'));
 
        假如一套RAC環境,在8月5日的9:00—09:30時,2節點發生了CPU消耗非常高的情況,如果要分析是不是因為SQL_ID為bj75p9188y410 的語句的執行計划走錯所致,這時,如果想用dbms_xplan.display_awr的簡單查詢方式來得到當時的執行計划,是無法實現的,那應該怎樣查出該語句8月5日的9:00—09:30時第2節點上SQL_ID為bj75p9188y410的語句的執行計划是怎樣子的呢?

 

2、分析
        如果通過DBMS_XPLAN.DISPLAY_AWR查看SQL語句的執行計划,將是從整個AWR數據庫中查找,例如從AWR報告中查詢SQL_ID為bj75p9188y410 的執行計划:
          SQL>select * from table(dbms_xplan.display_awr(db_id=> 19948XXXX2,sql_id=> 'bj75p9188y410'))

 

        為了以簡短的篇幅展示出從AWR中總共查到了幾種執行計划,我將語句改寫如下:
          SQL> select * from table(dbms_xplan.display_awr(db_id=> 19948XXXX2,sql_id=> 'bj75p9188y410')) where      
               plan_table_output  like ('Planhash value%');
  結果如下:
  
1
  
Plan hash value: 6178145
2
Plan hash value: 2354817963
3
Plan hash value: 3990363694
        從此結果中看出,SQL_ID為bj75p9188y410 的語句在當前保留的AWR數據中存在三種執行計划。其中Plan hash value為3990363694的執行計划為錯誤的執行計划
實際生產環境中,在8月5日的9:00—09:30時,2節點發生了CPU消耗非常高的情況。現在就是要確認在此時間,該SQL_ID為bj75p9188y410的語句到底是使用哪個執行計划呢?

 

3、解決方法
3.1 、查到8月5日9:00—09:30的 snap_id
SQL>select dbid,snap_id,instance_number,begin_interval_time,end_interval_time
     fromdba_hist_snapshot
    wherebegin_interval_time >=to_date('2013-08-0509:00:00', 'yyyy-mm-dd hh24:mi:ss')
      andend_interval_time <=to_date('2013-08-0509:31:00', 'yyyy-mm-dd hh24:mi:ss')
結果為:
  
dbid
  
snap_id
instance_number
begin_interval_time
end_interval_time
19948XXXX2
33676
1
05-8月 -13 09.00.09.903
05-8月 -13 09.30.10.113
19948XXXX2
33676
2
05-8月 -13 09.00.09.786
05-8月 -13 09.30.10.502

 

3.2 、通過 dbms_xplan.display_awr   與包含snap_id、instance_number信息的視圖關聯得到8月5日9:00—09:30時SQL_ID為 bj75p9188y410   的執行計划:
  SQL>select a.* from (select distinct dbid,sql_id, plan_hash_value from dba_hist_sqlstat
          wheresql_id = 'bj75p9188y410'
            andsnap_id =    33676
            andinstance_number = 2) b,          table ( dbms_xplan.display_awr ( db_id =>   19948XXXX2 , sql_id =>  b.sql_id , plan_hash_value => b.plan_hash_value ))  a;
結果如下:
SQL_ID bj75p9188y410
--------------------
select * from ( select distinct b.XXXX_id as
……   (為了信息脫敏,真實語句在此省略)
,'NLS_SORT=SCHINESE_XXXX'),b.XXXX_name  ) where rownum <= :1
 
Plan hash value: 3990363694
---------------------------------------------------------------------------------------------------------------------
| Id  |  Operation                                              | Name                                    | Rows     | Bytes | Cost (%CPU)| Time       |
---------------------------------------------------------------------------------------------------------------------
|   0 | SELECT  STATEMENT                             |                                             |            |           |      315 (100)|                 |
|   1 |  COUNT STOPKEY                                  |                                             |            |           |                     |                 |
|   2 |   VIEW                                                   |                                                |         1 |    180 |        315   (2)| 00:00:29 |
|   3 |    SORT ORDER BY STOPKEY                 |                                           |         1 |    151 |        315   (2)| 00:00:29 |
|   4 |     HASH UNIQUE                                    |                                              |         1 |    151 |        314   (1)| 00:00:29 |
|   5 |      FILTER                                              |                                              |            |           |                      |                |
|   6 |       NESTED LOOPS OUTER                   |                                           |         1 |    151 |         313   (1)| 00:00:29 |
|   7 |        NESTED LOOPS                              |                                            |         1 |      86 |           35   (0)| 00:00:04 |
|   8 |         TABLE ACCESS BY INDEX ROWID | LB_T_XXXX_PROVIDER            |        1 |      61 |           34    (0)| 00:00:04 |
|   9 |          INDEX RANGE SCAN                     | IDX_LB_T_XXXX_PROVIDER_003 | 183 |         |             3    (0)| 00:00:01 |
|  10 |         TABLE ACCESS BY INDEX ROWID | LA_XXXX                                 |         1 |      25 |            1    (0)| 00:00:01 |
|  11 |          INDEX UNIQUE SCAN                   | PK_LA_XXXX                           |         1 |           |            0    (0)|                 |
|  12 |        VIEW PUSHED PREDICATE            | LB_T_XXXX                             |         1 |      65 |         278   (1)| 00:00:26 |
|  13 |         MERGE JOIN OUTER                     |                                                |         1 |      64 |         278   (1)| 00:00:26 |
|  14 |          TABLE ACCESS BY INDEX ROWID| XXXX_SUPPLIER                     |         1 |      45 |        146    (0)| 00:00:14 |
|  15 |           INDEX FULL SCAN                       | PK_XXX_SUPPLIER                 |         1 |           |        145    (0)| 00:00:14 |
|  16 |          SORT JOIN                                  |                                                | 17998 |   333K|         132   (2)| 00:00:12 |
|  17 |           VIEW                                          |                                               | 17998 |   333K|         131   (1)| 00:00:12 |
|  18 |            SORT GROUP BY                       |                                                | 17998 |   544K|         131   (1)| 00:00:12 |
|  19 |             TABLE ACCESS FULL                | XXXX_SUPPLIER_CONTACT     | 30058 |   909K|         130   (0)| 00:00:12 |
---------------------------------------------------------------------------------------------------------------------
    此執行計划發生了嚴重的估算錯誤

 

###########3

沒啥幫助

 

http://www.oracle-wiki.net/startscriptsplanmonitor

 

A Script to Monitor Plan Changes

Description

The following script can be used monitor and alert on plan changes. Details of its use can be found in the headers of the script.

Plan_Change_Alert.ksh

#!/bin/ksh -x
############################################################################
#
# Author      : Mark Ramsay
#
# History       Date          Name          Reason
#               ----          ----          ------
#               18 May 2011   Mark Ramsay   Version 1.
#
# Description
#
# This script generates a report that shows if a SQL Plan has changed
# for a given SQL ID.  It is useful for tracking plans for stubborn pieces
# of SQL that may have a few good plans and the occasional bad plan.
#
# The range of dates can be changed by setting SDS_range.  However,
# this script would normally be scheduled each day the range will therefore
# be 1.  i.e. Changes in the last 24hrs
#
# The user should set the variable SDS_sqlid to the SQLID that is being
# monitored.  The variable SDS_hash_values should be set to the
# plan_hash_values that are acceptable for the given SQLID.
#
# If a new plan_hash_value is generated for the given SQLID, then
# the script will highlight this in the report.
#
# The report can then be mailed out to individuals to look into the plan
# change.
#
############################################################################
#
# Define Variables
#

export ORACLE_SID=MYSID
export ORACLE_HOME=$(grep ^$ORACLE_SID: /var/opt/oracle/oratab |awk -F\: '{print $2}')
export ORACLE_BASE=/u01/app/oracle
export PATH=.:/usr/local/bin:/bin:/usr/sbin:/usr/bin:$ORACLE_HOME/bin

SDS_date=`/bin/date '+%e_%B_%Y'|sed -e 's/ //'`
SDS_sqlid="'SQLID1','SQLID2'"
SDS_hash_values="HASH1,HASH2"
SDS_mail_addr=myemail@mydomain.com
SDS_range=1

SDS_output=`$ORACLE_HOME/bin/sqlplus -s '/ as sysdba' <<EOF

set pagesize 0
set feedback off
set linesize 128
set heading off
set echo off

SELECT distinct PLAN_HASH_VALUE
FROM dba_hist_sqlstat q,
    (
    SELECT /*+ NO_MERGE */ MIN(snap_id) min_snap, MAX(snap_id) max_snap
    FROM dba_hist_snapshot ss
    WHERE ss.begin_interval_time BETWEEN (SYSDATE - $SDS_range) AND SYSDATE
    ) s
WHERE q.snap_id BETWEEN s.min_snap AND s.max_snap
  AND q.sql_id IN ( $SDS_sqlid)
  AND q.plan_hash_value not in
($SDS_hash_values)
/

exit;
EOF`

if [ -z "$SDS_output" ];
then
  echo "All,
    Explain Plan Change for SQLIDs: $SDS_sqlid        -    No

Regards
    " | mailx -s "Explain Plan Alert Report $SDS_date" $SDS_mail_addr
else
  echo "All,
    Explain Plan Change for SQLIDs: $SDS_sqlid        -    Yes
    DBA to investigate.
    Plan Hash Values: $SDS_output

Regards
    " | mailx -s "Explain Plan Alert Report $SDS_date" $SDS_mail_addr
fi

exit 0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM