參考資料:官方文檔SQL Tuning Guide
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
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監控告警:
2、開發人員主動找到DBA說有慢SQL
3、數據庫出現性能問題告警
阻塞會話告警
活躍會話數告警
CPU、IO等告警
分析並優化慢SQL
開發人員反饋某個應用的SQL卡住了, 一直未返回結果
現象:開發人員發現某業務SQL沒有反應,應用接口其它SQL正常。 DBA接收到阻塞會話和活躍會話告警信息。
一般是dba先接收到告警。這時候可以先去查看活躍會話,看看數據庫當前節點在忙些啥?
接收到的告警:
同一時間開發人員反饋執行有問題的SQL
問題原因分析:
造成活躍會話升高原因基本上都是被瓶頸問題阻塞了,常見的有頻次高的慢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';
查詢結果如下:
從活躍會話查詢結果中看到,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;
執行結果:
接下來在看看會話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'));
發現是阻塞者和被阻塞者都是在更新同一張表中的某些行數據,更新到相同的行就會造成行鎖衝突。解決也很簡單,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
最後分享一個丁俊老師的一篇文章: