Skip to content

在Oracle监控中如何捕获执行计划变更的语句

在通常的数据库运维中,经常担心数据库一些重要的SQL的性能突然变差,导致整个数据库性能糟糕响应缓慢,甚至让数据库瘫痪的情况时有出现,往往这个时候要在事后通过awrsqrpt.sql脚本从top sql中去抓取分析执行变化的SQL语句,今天脑袋一闪想有没有办法有脚本可以自己去对比呢?回家就开始敲命令测试,测试的平台为11gr1版本,通过dba_hist_sqlstats数据字典中抓去sql id数据进行对比,通过对比获取相关的SQL_ID,以及此SQL的最优执行计划是什么。通过该sql可以设定一个固定的执行时间,通过分别查出来的数据进行监控,对执行计划产生误差的语句及时的通过SQL_PROFILE或者SPM(11g)调整成最优执行计划。

具体语句如下:

spool sql_have_logs_plan.txt
set lines 220 pages 9999 trimspool on
set numformat 999,999,999
column plan_hash_value format 99999999999999
column min_snap format 999999
column max_snap format 999999
column min_avg_ela format 999,999,999,999,999
column avg_ela format 999,999,999,999,999
column ela_gain format 999,999,999,999,999
select sql_id,
       min(min_snap_id) min_snap,
       max(max_snap_id) max_snap,
       max(decode(rw_num,1,plan_hash_value)) plan_hash_value,
       max(decode(rw_num,1,avg_ela)) min_avg_ela,
       avg(avg_ela) avg_ela,
       avg(avg_ela) - max(decode(rw_num,1,avg_ela)) ela_gain,
       -- max(decode(rw_num,1,avg_buffer_gets)) min_avg_buf_gets,
       -- avg(avg_buffer_gets) avg_buf_gets,
       max(decode(rw_num,1,sum_exec))-1 min_exec,
       avg(sum_exec)-1 avg_exec
from (
  select sql_id, plan_hash_value, avg_buffer_gets, avg_ela, sum_exec,
         row_number() over (partition by sql_id order by avg_ela) rw_num , min_snap_id, max_snap_id
  from
  (
    select sql_id, plan_hash_value , sum(BUFFER_GETS_DELTA)/(sum(executions_delta)+1) avg_buffer_gets,
    sum(elapsed_time_delta)/(sum(executions_delta)+1) avg_ela, sum(executions_delta)+1 sum_exec,
    min(snap_id) min_snap_id, max(snap_id) max_snap_id
    from dba_hist_sqlstat a
    where exists  (
       select sql_id from dba_hist_sqlstat b where a.sql_id = b.sql_id
         and  a.plan_hash_value != b.plan_hash_value
         and  b.plan_hash_value > 0)
    and plan_hash_value > 0
    group by sql_id, plan_hash_value
    order by sql_id, avg_ela
  )
  order by sql_id, avg_ela
  )
group by sql_id
having max(decode(rw_num,1,sum_exec)) > 1
order by 7 desc
/
spool off
clear columns
set numformat 9999999999

具体的执行结果参考如下:

SQL> spool sql_with_more_than_1plan.txt
set lines 220 pages 9999 trimspool on
SQL> SQL> set numformat 999,999,999
SQL> column plan_hash_value format 99999999999999
SQL> column min_snap format 999999
SQL> column max_snap format 999999
SQL> column min_avg_ela format 999,999,999,999,999
SQL> column avg_ela format 999,999,999,999,999
SQL> column ela_gain format 999,999,999,999,999
SQL> select sql_id,
2 min(min_snap_id) min_snap,
3 max(max_snap_id) max_snap,
4 max(decode(rw_num,1,plan_hash_value)) plan_hash_value,
5 max(decode(rw_num,1,avg_ela)) min_avg_ela,
6 avg(avg_ela) avg_ela,
7 avg(avg_ela) - max(decode(rw_num,1,avg_ela)) ela_gain,
8 -- max(decode(rw_num,1,avg_buffer_gets)) min_avg_buf_gets,
9 -- avg(avg_buffer_gets) avg_buf_gets,
10 max(decode(rw_num,1,sum_exec))-1 min_exec,
11 avg(sum_exec)-1 avg_exec
12 from (
13 select sql_id, plan_hash_value, avg_buffer_gets, avg_ela, sum_exec,
14 row_number() over (partition by sql_id order by avg_ela) rw_num , min_snap_id, max_snap_id
15 from
16 (
17 select sql_id, plan_hash_value , sum(BUFFER_GETS_DELTA)/(sum(executions_delta)+1) avg_buffer_gets,
18 sum(elapsed_time_delta)/(sum(executions_delta)+1) avg_ela, sum(executions_delta)+1 sum_exec,
19 min(snap_id) min_snap_id, max(snap_id) max_snap_id
20 from dba_hist_sqlstat a
21 where exists (
22 select sql_id from dba_hist_sqlstat b where a.sql_id = b.sql_id
23 and a.plan_hash_value != b.plan_hash_value
24 and b.plan_hash_value > 0)
25 and plan_hash_value > 0
26 group by sql_id, plan_hash_value
27 order by sql_id, avg_ela
28 )
29 order by sql_id, avg_ela
30 )
31 group by sql_id
32 having max(decode(rw_num,1,sum_exec)) > 1
33 order by 7 desc
34 /
spool off
clear columns
set numformat 9999999999
SQL_ID MIN_SNAP MAX_SNAP PLAN_HASH_VALUE MIN_AVG_ELA AVG_ELA ELA_GAIN MIN_EXEC AVG_EXEC
------------- -------- -------- --------------- -------------------- -------------------- -------------------- ------------ ------------
gfjvxb25b773h 192 204 3801470479 6,368 171,152 164,784 1 1
7asddw0r2wykx 192 211 3660107835 10,311 16,448 6,137 57 30
7ng34ruy5awxq 192 209 2606284882 117 320 204 426 1,371
g278wv0zzpn5j 195 211 3142739690 0 189 189 72 63
1gu8t96d0bdmu 192 209 2035254952 67 154 87 826 1,134
bsa0wjtftg3uw 193 211 2020579421 0 18 18 224 191
0kugqg48477gf 201 202 3321035584 0 0 0 19 27

7 rows selected.

通过以上查询我们发现了7个结果,其中MIN_SNAP和MAX_SNAP代表该sql_id所代表最早出现时候的快照和最后一次出现的快照;PLAN_HASH_VALUE代表着最佳的执行计划;ELA_GAIN字段代表如果使用最佳的执行计划,与平均执行时间能够提速多少时间。

在查出结果后,我们可以通过sql profile(参考老刘的《验证sql_profile的执行计划》),也可以通过11g的dbms_spm.load_plans_from_cursor_cache更改相关Sql_id的sql的执行计划,这里以11g为例子做如下变更,对上面查询到的7asddw0r2wykx语句执行计划改为最优:

SQL> variable v_sqlid number
SQL> begin
2 :v_sqlid :=
3 dbms_spm.load_plans_from_cursor_cache(
4 sql_id=>'&sql_id',
5 plan_hash_value=>&plan_hash,
6 fixed=>'YES');
7 end;
8 /
Enter value for sql_id: 7asddw0r2wykx
old 4: sql_id=>'&sql_id',
new 4: sql_id=>'7asddw0r2wykx',
Enter value for plan_hash: 3660107835
old 5: plan_hash_value=>&plan_hash,
new 5: plan_hash_value=>3660107835,

PL/SQL procedure successfully completed.