oracle中慢sql優化思路

參考資料:官方文檔SQL Tuning Guide

https://docs.oracle.com/en/database/oracle/oracle-database/12.2/tgsql/sql-performance-fundamentals.html#GUID-DD9CAA74-3E0B-48C9-8770-AADB614BC992

Oracle Database 2 Day + Performance Tuning Guide

Oracle Performance Tuning Guide

如何發現慢SQL

主動發現

DBA和開發人員均可通過平臺來發現某時間段、某數據庫實例上的慢SQL信息。但平臺中只能簡單的查看一些執行計劃以及執行過程的統計信息,需要更詳細信息還是要去數據庫查詢,目前慢優化這塊待完善。

通過ASH&AWR去發現

從ash查看某段時間SQL的等待總次數,CPU、IO等維度

col type for a10 
select * from ( 
select 
     ash.SQL_ID , ash.SQL_PLAN_HASH_VALUE Plan_hash, aud.name type, 
     sum(decode(ash.session_state,'ON CPU',1,0))     "CPU", 
     sum(decode(ash.session_state,'WAITING',1,0))    - 
     sum(decode(ash.session_state,'WAITING', decode(wait_class, 'User I/O',1,0),0))    "WAIT" , 
     sum(decode(ash.session_state,'WAITING', decode(wait_class, 'User I/O',1,0),0))    "IO" , 
     sum(decode(ash.session_state,'ON CPU',1,1))     "TOTAL" 
from v$active_session_history ash, 
     audit_actions aud 
where SQL_ID is not NULL  
   and ash.sql_opcode=aud.action 
   and ash.sample_time > sysdate - &minutes /( 60*24) --最近幾分鐘的時間範圍
 --and ash.sample_time between to_timestamp('&begin_time','yyyy-mm-dd hh24:mi:ss') and to_timestamp('&end_time','yyyy-mm-dd hh24:mi:ss') --某段時間範圍
group by sql_id, SQL_PLAN_HASH_VALUE   , aud.name 
order by sum(decode(session_state,'ON CPU',1,1))   desc 
) where  rownum < 20;  --取TOP 20條等待次數最多sql

從AWR報告查看TOP SQL

image.png

awr中重點關注某問題段時間一般間隔爲15分鐘,top sql,主要關注平均每次執行的時間以及執行sql耗用資源情況。

 

按照某top sql維度從awr基表中批量獲取慢SQL

適合做營銷活動前主動的從awr資料庫裏面抓取最近幾天的所有慢SQL

select dbms_lob.substr(sql_text, 100) sqla, AA.*, BB.SQL_TEXT
  from (select sql_id,
               plan_hash_value,
               object_name,
               BUFFER_GETS,
               EXECUTIONS,
               BUFFER_GETS / decode(nvl(EXECUTIONS, 1), 0, 1, EXECUTIONS) as BUFFER_GETS_Per_Exec,
               DISK_READS / decode(nvl(EXECUTIONS, 1), 0, 1, EXECUTIONS) as DISK_READS_Per_Exec,
               ELAPSED_TIME / 1000000 as to_time,
               io_wait / 1000000 as io_time,
               round(io_wait / ELAPSED_TIME * 100) || '%' ioa_time,
               -- round(CPU_TIME/ELAPSED_TIME*100)||'%' cpua_time,
               row_processed / decode(nvl(EXECUTIONS, 1), 0, 1, EXECUTIONS) rows_processed_1exec,
               ELAPSED_TIME / decode(nvl(EXECUTIONS, 1), 0, 1, EXECUTIONS) /
               1000000 as ELAPSED_TIME_Per_Exec,
               CPU_TIME / decode(nvl(EXECUTIONS, 1), 0, 1, EXECUTIONS) /
               1000000 as CPU_TIME_Per_Exec
          from (select b.sql_id sql_id,
                        b.plan_hash_value,
                        o.object_name,
                        sum(nvl(b.EXECUTIONS_DELTA, 3)) as EXECUTIONS,
                        sum(nvl(b.DISK_READS_DELTA, 3)) as DISK_READS,
                        sum(nvl(b.iowait_DELTA, 3)) as io_wait,
                        sum(nvl(b.BUFFER_GETS_DELTA, 0)) as BUFFER_GETS,
                        sum(nvl(b.CPU_TIME_DELTA, 0)) as CPU_TIME,
                        sum(nvl(b.rows_processed_delta, 0)) as row_processed,
                        -- b.rows_processed_delta
                        sum(nvl(b.ELAPSED_TIME_DELTA, 0)) as ELAPSED_TIME
                   from DBA_HIST_SQLSTAT  b,
                        dba_hist_snapshot a,
                        dba_hist_sql_plan p,
                        dba_objects o
                  where /*b.sql_id in
                                                                          (select distinct (sql_id)
                                                                             from dba_hist_active_sess_history t
                                                                            where session_id in (708,978)
                                                                              and sql_id is not null
                                                                              and to_char(t.sample_time, 'yyyy-mm-dd hh24-mi-ss') >=
                                                                                  '2016-05-26 21-00-00'
                                                                              and to_char(t.sample_time, 'yyyy-mm-dd hh24-mi-ss')<=
                                                                                  '2016-05-26 23-50-00')
                                                                      and  */
                  b.snap_id = a.snap_id
               and b.parsing_schema_name in ('CCIC', 'CCICAGT')
               and b.instance_number = a.instance_number
               and b.sql_id = p.sql_id
              -- and p.options = 'FULL'
               and p.object_name=o.object_name
               and to_char(a.begin_interval_time, 'yyyy-mm-dd hh24-mi-ss') >=
                  '2016-06-02 09-00-00'
               and to_char(a.end_interval_time, 'yyyy-mm-dd hh24-mi-ss') <=
                  '2016-06-02 17-40-00'
                 --and b.snap_id >= 67040
                 -- and b.snap_id <= 67050
                  group by b.sql_id, b.plan_hash_value,o.object_name)) aa,
       dba_hist_sqltext bb
 where AA.sql_id = BB.sql_id
   and BUFFER_GETS_Per_Exec > 10000
 order by -- to_time desc
          BUFFER_GETS_Per_Exec desc

被動發現

1、慢SQL監控告警:

image.png

2、開發人員主動找到DBA說有慢SQL

 

3、數據庫出現性能問題告警

阻塞會話告警

活躍會話數告警

CPU、IO等告警

分析並優化慢SQL

開發人員反饋某個應用的SQL卡住了, 一直未返回結果

現象:開發人員發現某業務SQL沒有反應,應用接口其它SQL正常。 DBA接收到阻塞會話和活躍會話告警信息。

一般是dba先接收到告警。這時候可以先去查看活躍會話,看看數據庫當前節點在忙些啥?

接收到的告警:

image.png

image.png

同一時間開發人員反饋執行有問題的SQL

image.png

問題原因分析:

造成活躍會話升高原因基本上都是被瓶頸問題阻塞了,常見的有頻次高的慢SQL,應用接連不斷的發送sql 但執行比較慢,累積的越來越多活躍會話。阻塞會話過多,8成是遇到鎖特別是行鎖。

先看看活躍會話情況:

set linesize 200 
col sid format 999999 
col s# format 9999999 
col username format a15 
col event format a40  
col BLOCKING_SESSION format 999999  
col machine format a20 
col p123 format a30 
col wt format 999 
col spid format a15 
col SQL_ID for a18 
   SELECT /* XJ LEADING(S) FIRST_ROWS */
    S.SID,
    S.SERIAL# S#,
    S.USERNAME,
    S.MACHINE,
    S.EVENT,
    S.BLOCKING_SESSION,
    S.P1 || '/' || S.P2 || '/' || S.P3 P123,
    S.WAIT_TIME WT,
    NVL(SQL_ID, S.PREV_SQL_ID) SQL_ID
     FROM V$SESSION S
    WHERE S.STATUS = 'ACTIVE' and S.TYPE <>'BACKGROUND';

查詢結果如下:

image.png

從活躍會話查詢結果中看到,sql ba2wr7m4xcrzx的等待事件都是關於行鎖的enq:Tx - row lock contention,並且阻塞者的會話是6829,阻塞源頭基本斷定是6829,後面看看會話6829在幹啥。

執行查詢sql:

set linesize 200 
col sid format 999999 
col s# format 9999999 
col username format a15 
col event format a40  
col BLOCKING_SESSION format 999999  
col machine format a20 
col p123 format a30 
col wt format 999 
col spid format a15 
col SQL_ID for a18 
col PROGRAM for a18 
col MODULE for a18 
alter session set cursor_sharing=force; 
   SELECT /* XJ LEADING(S) FIRST_ROWS */
    S.inst_id,
    S.SID,
    S.SERIAL# S#,
    S.USERNAME,
    S.MACHINE,
    S.PROGRAM,
    S.MODULE,
    S.EVENT,
    S.BLOCKING_SESSION,
    S.P1 || '/' || S.P2 || '/' || S.P3 P123,
    S.WAIT_TIME WT,
    NVL(SQL_ID, S.PREV_SQL_ID) SQL_ID
     FROM gV$SESSION S
    WHERE S.TYPE <>'BACKGROUND' 
       and S.sql_id = '&sql_id'
       order by 1,2; 

執行結果:

image.png

接下來在看看會話6829上sql 5haaxd3zxbqgc在跑啥?

select sql_id,sql_fulltext from v$sql where sql_id='5haaxd3zxbqgc' and rownum=1;
或者直接查看執行計劃以及sql文本,看的信息更多一些
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(to_char('&SQL_ID'),&child_NULL,'ADVANCED'));

image.png

發現是阻塞者和被阻塞者都是在更新同一張表中的某些行數據,更新到相同的行就會造成行鎖衝突。解決也很簡單,kill掉阻塞源頭就可以,但DBA這個時候要作出評估。

1)、立馬把SQL語句丟到開發溝通羣,快速詢問 這是阻塞源頭是否可以立馬kill掉,請儘快評估kill掉對業務是否有影響

2)、多次查詢活躍會話,持續關注該庫上的告警信息,看活躍會話和阻塞會話是否一直在快速增加

如果活躍會話和阻塞會話一直增加,數據庫性能不可控。DBA要果斷kill該阻塞源頭。

alter system kill session '6829,43685' immediate;
或者通過sql_id生成相關kill語句
SELECT q'[alter system kill session ']'||S.SID||','||S.SERIAL#||q'[' immediate;]' sql_text from V$SESSION S
 WHERE S.sql_id = '&sql_id' 
   AND S.STATUS = 'ACTIVE'; 

如果數據庫性能暫時可控,告知開發後果後,等待他們答覆後再處理。等開發人員做好準備工作後就可以kill該會話。

 

收尾工作:

持續關注該庫上的告警信息,同時關注因kill掉了大事物的DML語句,關注數據庫回滾情況。

alter session set NLS_DATE_FORMAT='DD-MON-YYYY HH24:MI:SS'; 
 select usn, state, undoblockstotal "Total", undoblocksdone "Done", undoblockstotal-undoblocksdone "ToDo", 
            decode(cputime,0,'unknown',sysdate+(((undoblockstotal-undoblocksdone) / (undoblocksdone / cputime)) / 86400)) 
             "Estimated time to complete" 
  from v$fast_start_transactions; 

如果回滾事物太慢,可以考慮調整參數:

alter system set "_rollback_segment_count" = 2000;

 

開發人員反饋同樣SQL昨天好好,今天突然變慢

一般分這幾種情況:

1)、執行計劃變了,最常見

2)、之前綁定的執行計劃,隨着數據量的增長已經不合適了。

3)、修改了數據庫參數,特別是優化器相關的參數,問題sql是定時跑的,並沒有立馬體現出來。比較少見。

分析思路與解決方案:

執行計劃抖動,綁定

開發人員給出的sql往往是sql文本,並且很有可能是同一張表雷同SQL,只是有細微差異,體現在數據庫中的是不同SQL_ID。這種情況不能完全相信開發人員給出的sql,一定要根據提供的信息去數據庫裏面再找找,把所有雷同的sql列出來。解決問題不僅要解決問題點,還要覆蓋到問題面。

覈對慢sql 看平臺上慢SQL,以及查v$SQL

select sql_id,sql_fulltext from v$sql where sql_text like '%sql註釋部分%'

查看sql執行情況,對比性能好時段和變差時段執行計劃變更情況

col PLAN_HASH_VALUE for 9999999999 
col instance_number for 9 
col snap_id heading 'SnapId' format 999999 
col executions_delta heading "No. of exec" 
col date_time heading 'Date time' for a20 
col avg_lio heading 'LIO/exec' for 999999999999 
col avg_cputime_s heading 'CPUTIM/exec' for 99999 
col avg_etime_s heading 'ETIME/exec' for 999999 
col avg_pio heading 'PIO/exec' for 999999999 
col avg_row heading 'ROWs/exec' for 9999999999 
col sql_profile format a35 
SELECT distinct 
s.snap_id , 
s.instance_number, 
PLAN_HASH_VALUE, 
to_char(s.BEGIN_INTERVAL_TIME,'mm/dd/yy_hh24mi')|| to_char(s.END_INTERVAL_TIME,'_hh24mi') Date_Time, 
SQL.executions_delta, 
SQL.buffer_gets_delta/decode(nvl(SQL.executions_delta,0),0,1,SQL.executions_delta) avg_lio, 
(SQL.cpu_time_delta/1000000)/decode(nvl(SQL.executions_delta,0),0,1,SQL.executions_delta) avg_cputime_s , 
(SQL.elapsed_time_delta/1000000)/decode(nvl(SQL.executions_delta,0),0,1,SQL.executions_delta) avg_etime_s, 
SQL.DISK_READS_DELTA/decode(nvl(SQL.executions_delta,0),0,1,SQL.executions_delta) avg_pio, 
SQL.rows_processed_total/decode(nvl(SQL.executions_delta,0),0,1,SQL.executions_delta) avg_row, 
SQL.sql_profile 
FROM dba_hist_sqlstat SQL,dba_hist_snapshot s 
WHERE 
SQL.dbid =(select dbid from v$database) 
and s.snap_id = SQL.snap_id 
and sql.instance_number = s.instance_number 
AND sql_id in ('&sql_id') order by s.snap_id; 

如果結果中看出來執行計劃變更了,那就要考慮把問題sql的執行計劃綁定。

使用COE腳本綁定步驟:

腳本下載地址:https://github.com/AlbertCQY/scripts/blob/master/oracle/sql_profile_new2.sql

腳本簡單說明:原始coe腳本出自oracle MOS官方,sql_profile_new2.sql腳本是oracle官方高級售後DBA修改的增強版。可以綁定執行計劃、替換執行計劃。

@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_id和plan hash
比如:coe_xfr_sql_profile_62159umsg6z8m_4105682492.sql
綁定執行計劃就直接執行上面生成的腳本。

刷新sql執行計劃遊標:

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';
 得到刷新語句後直接執行即可。
                                                                                            

綁定執行計劃後重新查看下sql執行計劃信息,如果還是原來的執行計劃則有可能是coe綁定成功了,但由於sql正在執行中 導致執行計劃遊標刷出失敗。需要和開發溝通是否可以kill掉正在執行sql的會話,然後再去刷新即可。

 

構造新的執行計劃,解綁->綁定新的

如果發現sql上面已經綁定了執行計劃,但隨着表上數據量的增長,以及業務邏輯的變更,綁定的執行計劃已經不適合了,需要解綁並替換爲更優的執行計劃。

構造想要的執行計劃:hint提示方法

由於業務評估失誤以及數據量的不斷增長,該sql在項目開始時候評估下來適合走object_id列上的索引,並且也做了執行計劃的綁定。

現在業務數據產生了變化,需要按照預定方式走object_name列上的索引idx_name

原來sql(fvscnttfnqvkf)  select * from t_testplan where object_id=1 and object_name='test'
Plan hash value: 2317386271

------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     8 |  1656 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T_TESTPLAN |     8 |  1656 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_ID     |    14 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

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

   1 - filter("OBJECT_NAME"='test')
   2 - access("OBJECT_ID"=1)
加hint後sql(9xtcn2g6n7gsw)  select /*+INDEX(t_testplan idx_name) */ * from t_testplan where object_id=1 and object_name='test'
Plan hash value: 1801285354

--------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |     7 |  3367 |    12   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T_TESTPLAN |     7 |  3367 |    12   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_NAME   |    16 |       |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

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

   1 - filter("OBJECT_ID"=1)
   2 - access("OBJECT_NAME"='test')

現在需要把fvscnttfnqvkf的執行計劃替換爲9xtcn2g6n7gsw的執行計劃

 

第一步:刪除綁定的執行計劃(解綁profile)

 

select name from dba_sql_profiles where name like '%fvscnttfnqvkf%';

BEGIN 
DBMS_SQLTUNE.DROP_SQL_PROFILE(name => 'SYS_SQLPROF_fvscnttfnqvkf'); 
END; 
/

 

第二步:綁定執行計劃

@sql_profile_new2.sql
Parameter 1:
SQL_ID (required)

Enter value for 1:  --這裏傳入需要優化的sqlid fvscnttfnqvkf
Parameter 2:
PLAN_HASH_VALUE (required)  

Enter value for 2:  --這裏傳入加Hint後的9xtcn2g6n7gsw執行計劃PLAN_HASH_VALUE 1801285354

參照之前的步驟,刷新執行計劃遊標。

 

開發人員反饋應用OOM告警,需要看下SQL是否有異常

場景分析:應用server內存OOM後,開發人員在分析應用代碼以及框架沒問題後,一般會找DBA查找SQL的原因。

1)、開發人員提供的SQL有很明顯的全表掃描語句

比較少見,一般添加合適的索引即可。

2)、開發人員提供的帶綁定變量的sql,並且dba提供了完整測試語句

開發提供的sql在數據庫上測試了下,性能很好,返回的結果集也很小。但真的是這樣麼?這時候就要懷疑是不是沒有給到出現OOM時綁定變量真正的傳參值。

出現這種比較奇怪的信息不對稱情況時,其實也挺好求證。查看該SQL歷史執行情況,和之前的邏輯讀、物理讀、返回行數等對比下就知道了。如果問題時段這些指標相對高,那麼八九不離十就是傳參傾斜導致。

 

新上線了功能,第二天發現一堆性能告警

場景分析:新上線的SQL由於性能評估不到位,過段時間在業務高峯時段,逐漸暴露出來性能問題。

常見有缺少必要的索引:DBA根據表結構以及各列的統計信息來判斷,下面分享兩個常用的腳本

 

表維度,查看錶上結構信息、統計信息等,tabstat.sql腳本:傳入用戶名+表名

https://github.com/AlbertCQY/scripts/blob/master/oracle/tabstat.sql

SQL維度,SQL語句所有關聯的表上結構信息、統計信息等,sql10.sql腳本:傳入sql_id

https://github.com/AlbertCQY/scripts/blob/master/oracle/sql10.sql

 

創建索引指導建議:

適合創建索引的列

  • 索引覆蓋(只select索引列)、避免排序(order by索引列)
  • 複合索引儘量兼顧更多SQL(索引具有較多的使用場景)
  • 該列在表中的唯一性特別高、有些狀態列有傾斜值(符合少數)
  • 複合索引等值謂詞條件字段做前導列,非等值謂詞條件字段放在後面
  • 表關聯使用Nested Loop 被驅動表的關聯字段上建議創建索引
  • SQL語句是主流的業務,具有高併發,where條件中出現的列可以考慮創建複合索引

不適合創建索引的列

  • DML頻繁的表不適合創建索引,索引會帶來額外的維護成本
  • 爲了少數查詢,並且頻次不高的查詢列上建索引(這類SQL考慮放讀庫執行)
  • Where條件中不會使用的列也不適合創建索引

 

 

如何解決一條複雜的SQL

Oracle數據庫不僅對OLTP型短平快的sql支持很好,OLAP型複雜的分析SQL同樣支持很好。一般來說複雜SQL執行計劃特別長,甚至超過200行,關聯5張以上表或視圖,無法快速分析出執行計劃是否有問題,甚至執行計劃還經常抖動。

 

優化思路:不管SQL寫的多複雜,執行計劃超級長,只需要抓住sql最影響性能的地方即可。

藉助腳本plan_ash.sql或者sql10.sql腳本可以展示出最消耗性能的部分:https://github.com/AlbertCQY/scripts/blob/master/oracle/plan_ash.sql

比如下面這個執行計劃,發現性能瓶頸在邏輯讀上面,優化掉db file sequential read(2)(40%) 這一步驟的性能問題,該複雜SQL問題也就解決了。

 

Oracle官方工具篇:

Oracle官方提供了豐富的sql調優工具,面對複雜SQL善於使用官方提供的工具也是個不錯的方法。

Oracle真的是博大精深,學習永無止境...

Information Center: Sql Performance Tuning: Troubleshoot (Doc ID 1516522.2)

 

SQL Tuning Advisor:

SQL Tuning Advisor (Doc ID 2582636.1)

Automatic SQL Tuning and SQL Profiles (Doc ID 271196.1)

Using the DBMS_SQLTUNE Package to Run the SQL Tuning Advisor (Doc ID 262687.1)

Example: SQL Tuning Task Options (Doc ID 2461848.1)

SQL Performance Analyzer Summary (Doc ID 1577290.1)

 

SQL Tuning Health-Check Script (SQLHC) (Doc ID 1366133.1)

NOTE:243755.1 - Script to produce HTML report with top consumers out of PL/SQL Profiler DBMS_PROFILER data

NOTE:1482811.1 - Best Practices: Proactively Avoiding Database and Query Performance Issues

NOTE:1460440.1 - Script PXHCDR.SQL: Parallel Execution Health-Checks and Diagnostics Reports

NOTE:1477599.1 - Best Practices: Proactive Data Collection for Performance Issues

NOTE:224270.1 - TRCANLZR (TRCA): SQL_TRACE/Event 10046 Trace File Analyzer - Tool for Interpreting Raw SQL Traces (NO LONGER SUPPORTED - Use SQLTXPLAIN sqltrcanlzr.sql)

NOTE:1627387.1 - How to Determine the SQL_ID for a SQL Statement

NOTE:1455583.1 - SQL Tuning Health-Check Script (SQLHC) Video

NOTE:215187.1 - All About the SQLT Diagnostic Tool

NOTE:1417774.1 - FAQ: SQL Health Check (SQLHC) Frequently Asked Questions

 

最後分享一個丁俊老師的一篇文章:

https://dbaplus.cn/news-10-1314-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章