場景:
經常有開發人員反饋,在某段時間(甚至時間精確到秒) 反饋有業務接口超時問題。並不一定是某條sql慢,而是整個應用接口上都出現了慢sql問題。這時候一般是根據精確的時間點觀察等待事件和報錯日誌以及時間點前後數據庫在幹什麼事情來分析問題。
查看錯誤日誌信息+監控信息
重點查看的是數據庫alert日誌錯誤告警和操作系統錯誤日誌信息:
linux下舉例:/u01/app/oracle/diag/rdbms/orcl11g/orcl11g/trace/alert_orcl11g.log
/var/log/message
主要查看ORA-xxx ,error, faild,warning等關鍵錯誤信息。
問題時段監控信息:自由度比較大,依賴數據庫外部監控系統
user cpu、sys cpu、QPS&TPS、活躍會話信息、阻塞會話信息、邏輯讀、物理讀、磁盤IOPS等信息
OSwatcher信息作爲輔助
對於oracle瞬間抖動的性能問題,一般通過以上說的錯誤日誌和監控信息是看不錯啥結果的,基於Oracle強大的性能數據收集功能,可以通過AWR+ASH這兩個工具來分析具體某個時間段甚至時間點,基於等待事件的方法論來分析數據庫那個時間段在幹什麼,性能瓶頸在哪兒。
利用AWR分析數據庫負載以及主要性能瓶頸
適用場景:
數據庫整體性能問題出現的時間稍長,比如超過5分鐘。我們的AWR報告生成間隔是15分鐘,雖然15分鐘的跨度會把某些指標平均掉,但對於超過5分鐘的性能問題,awr報告還是可以捕捉到很多有用的性能指標。
看整體負載情況
看DB Time:
如上,awr報告開頭部分會看到數據庫實例、操作系統的一些關鍵信息,可以一眼看到數據庫跑在一個什麼樣的環境中。
這裏的CPU數是指邏輯CPU數(Number of logical CPUs available (includeshardwarethreads if hardwarethreadingis turned on))
當硬件線程技術打開時,例如:POWERSMT(SimultaneousMulti-Thread) 或者IntelHT(HyperThread)。此時一個CPU core被視作2個或更多個邏輯CPU。
緊接着一個重要的指標就是DB Time,一個經驗值就是如果DB Time大於 elapsed time * cores,一般代表這數據庫負載比較高,這份awr報告是值得仔細分析的。如果沒有超過這個數,一般都是極個別SQL性能差引起的,對整體負載並沒有引起太大的波動。上圖中15*32 = 480 mins,DB Time:2333.51 mins,很值得往下深入分析。
補充:DB Time是怎麼算出來的
DB TIME= DB CPU + Non-Idle Wait + Wait on CPU queue
- Totaltime in database calls by foreground sessions
- IncludesCPU time, IO time and non-idle wait time
- DBTime <> response time
- TotalDB time = sum of DB time for all active sessions
看load profile:
load profile主要反映的是在awr快照間隔內,數據庫在幹活的狀態下,各種度量指標,可以從QPS(Executes SQL)和TPS(Transactions)上看出數據庫對業務sql響應的大概情況。
下面 是各具體指標的解析:
指標 |
指標含義 |
redo size |
單位 bytes,redo size可以用來估量update/insert/delete的頻率,大的redo size往往對lgwr寫日誌,和arch歸檔造成I/O壓力, Per Transaction可以用來分辨是 大量小事務, 還是少量大事務。如上例每秒redo 約7.5MB ,每個事務3KB,基本可以判斷系統處於業務高峯期,批量處理的事物比較多。 |
Logical Read |
單位 次數*塊數, 相當於 “人*次”, 如上例 112550.2 * db_block_size(8k)=879MB/s , 邏輯讀耗CPU,主頻和CPU核數都很重要,邏輯讀高則DB CPU往往高,也往往可以看到latch: cache buffer chains等待。但這不是最高,有些繁忙OLTP系統可以高達幾十乃至上百Gbytes。 |
Block changes |
單位 次數*塊數 ,描述數據變化頻率 |
Physical Read |
單位次數*塊數, 如上例 14.3 * 8k = 114.4B/s,物理讀偏低,也說明該系統select比較少,主要以DML爲主。 物理讀消耗IO讀,體現在IOPS和吞吐量等不同緯度上;但減少物理讀可能意味着消耗更多CPU。好的存儲 每秒物理讀能力達到幾GB。 這個physical read包含了physical reads cache和physical reads direct |
Physical writes |
單位 次數*塊數,主要是DBWR寫datafile,也有direct path write。 dbwr長期寫出慢會導致定期log file switch(checkpoint no complete) 檢查點無法完成的前臺等待。 這個physical write 包含了physical writes direct +physical writes from cache |
User Calls |
單位次數,用戶調用數 |
Parses |
解析次數,包括軟軟解析+軟解析+硬解析,軟解析優化得不好,則誇張地說幾乎等於每秒SQL執行次數。 即執行解析比1:1,而我們希望的是 解析一次 到處運行。 |
Hard Parses |
硬解析是萬惡之源。伴隨着大量Cursor pin s on X, library cache: mutex X , latch: row cache objects /shared pool等。 硬解析最好少於每秒20次 |
W/A MB processed |
單位MB W/A workarea workarea中處理的數據數量 結合 In-memory Sort%, sorts (disk) PGA Aggr一起看 |
Logons |
登陸次數, logon storm 登陸風暴,結合AUDIT審計數據一起看。短連接的附帶效應是遊標緩存(session cached cursor)無用 |
Executes |
執行次數,反映的是執行頻率 |
Rollback |
回滾次數, 反映回滾頻率,作爲性能參考指標 |
Transactions |
每秒事務數,是數據庫層的TPS,可以看做壓力測試或比對性能時的一個指標,需要綜合的去看纔有意義 |
|
|
看命中率
Instance Efficiency Percentages (Target 100%)
還是貼成表格方便些
Buffer Nowait %: | 99.78 | Redo NoWait %: | 100.00 |
Buffer Hit %: | 100.00 | In-memory Sort %: | 100.00 |
Library Hit %: | 99.96 | Soft Parse %: | 100.00 |
Execute to Parse %: | 0.08 | Latch Hit %: | 99.71 |
Parse CPU to Parse Elapsd %: | 52.91 | % Non-Parse CPU: | 95.06 |
上面表格中標紅的指標的值越高越好。指標Parse CPU to Parse Elapsd的值比較低,說明該系統解析情況並不是很好。
Parse CPU to Parse Elapsd:解析實際運行時間/(解析實際運行時間+解析中等待資源時間),越高越好。計算公式爲:Parse CPU to Parse Elapsd %=100*(parse time cpu / parse time elapsed)。即:解析實際運行時間/(解析實際運行時間+解析中等待資源時間)。如果該比率爲 100%,意味着 CPU 等待時間爲 0,沒有任何等待。
Non-Parse CPU :SQL 實際運行時間/(SQL 實際運行時間+SQL 解析時間),太低表示解析消耗時間過多。計算公式爲:% Non-Parse CPU=round(100*1-PARSE_CPU/TOT_CPU),2)。
如果這個值比較小,表示解析消耗的 CPU 時間過多。與 PARSE_CPU 相比,如果 TOT_CPU 很高,這個比值將接近 100%,這是很好的,說明計算機執行的大部分工作是執行查詢的工作,而
不是分析查詢的工作。
看TOP等待事件
Top 10 Foreground Events by Total Wait Time
Event | Waits | Total Wait Time (sec) | Wait Avg(ms) | % DB time | Wait Class |
log file sync | 1,241,679 | 94.6K | 76 | 67.6 | Commit |
library cache: mutex X | 27,048,671 | 34.7K | 1 | 24.8 | Concurrency |
reliable message | 4,200,609 | 4092.4 | 1 | 2.9 | Other |
DB CPU | 3787.1 | 2.7 | |||
enq: TX - index contention | 84,828 | 372.2 | 4 | .3 | Concurrency |
latch: cache buffers chains | 65,945 | 301.1 | 5 | .2 | Concurrency |
buffer busy waits | 212,212 | 297.9 | 1 | .2 | Concurrency |
latch: ges resource hash list | 22,526 | 269.6 | 12 | .2 | Other |
enq: TX - row lock contention | 3,397 | 70.3 | 21 | .1 | Application |
cursor: pin S | 12,956 | 58.8 | 5 | .0 | Concurrency |
top等待事件是很重要的指標,顯示了那些可能構成系統瓶頸的數據庫等待事件。
首先檢查等待類是否爲用戶I/O,系統I/O等等,但如果等待類是併發性(Concurrency),那麼可能會有一些嚴重的問題。
接下來要看的是Total Wait Time(sec),它顯示了DB在這個類中等待了多少次,然後是Wait Avg(ms)。如果總等待時間高,但等待平均時間(ms)低,那麼可以忽略這個。如果兩者都是高的或平均等待是高的,則必須進行深入分析。
對於最大的等待,比如上面的log file sync等待,查看該等待事件直方圖(Wait Event Histogram)以確定等待的分佈。
該案例展示了,數據庫性能瓶頸在於log file sync,並且平均等待時間比較長,接下來就要去看等待事件直方圖。
從top等待事件分析問題的基礎在於熟悉每個等待事件指標的含義,等待事件具體代表啥請移步oracle官方MOS...
看等待事件直方圖(Wait Event Histogram)
分析:從64ms ~ 2sec 的直方圖中看出,99.2%的等待都是小於32ms的,這可能看不出啥問題,接着往下看。
4sec 2min的等待直方圖中看出,99.9%的都是小於2秒的,但是4s ~ 2min的等待卻有1485個。對於log file sync等待事件來說,寫日誌等了這麼久,反映在前端等待事件上。肯定會有嚴重問題的。最後也證實了因爲存儲光纖鏈路抖動導致。
看TOP SQL
Oracle數據庫中80%的問題都是SQL問題引起的,TOP SQL也是比較重要的指標,但也有可能awr快照範圍沒覆蓋某些問題sql,需要綜合的去分析。
sql order by elapsed time 主要看執行總時間以及執行次數,如果平均每次執行的時間很短,可以忽略掉。
其它類似指標也是同樣道理,如果sql單次執行時間很長,又跑在OLTP系統上,要麼優化掉,要麼挪到讀庫去執行,不能損耗寫庫寶貴的性能資源。
但是平均執行時間短,但是執行次數非常頻繁的SQL也要引起重視,因爲高頻次SQL稍微一抖動,就會引起阻塞風暴,拖垮系統性能。
利用ASH分析等待事件以及阻塞源頭
適用場景
在很多情況下,數據庫發生性能問題持續的時間比較短,比如持續了1秒 ~ 15秒。前面提供的幾種手段可能並不湊效。比如監控粒度粗(分鐘級別),AWR間隔長(15分鐘,平均掉關鍵信息)。可以通過ash來分析問題。
關於gv$active_session_history和dba_hist_active_sess_history:
從Oracle 10G開始,引入了AWR和ASH採樣機制,有一個視圖gv$active_session_history會每秒鐘將數據庫所有節點的Active Session採樣一次,而dba_hist_active_sess_history則會將gv$active_session_history裏的數據每10秒採樣一次並持久化保存。但是比基於這個特徵,我們可以通過分析dba_hist_active_sess_history的Session採樣情況,來定位問題發生的準確時間範圍,並且可以觀察每個採樣點的top event和top holder。
gv$active_session_history雖然沒有持久化,但是比dba_hist_active_sess_history保存的信息要全面。所以,在必要的時候備份下
create table ash_xxxx tablespace tbs_xxx as select * from gv$active_session_history where SAMPLE_TIME between TO_TIMESTAMP ('<time_begin>', 'YYYY-MM-DD HH24:MI:SS') and TO_TIMESTAMP ('<time_end>', 'YYYY-MM-DD HH24:MI:SS');
通過ash速查找阻塞源頭
腳本一:某比較短時間內等待次數統計,分鐘級別和秒級別
從問題發生點前後的某幾秒時間內觀察所有會話的等待事件,可以判斷某會話在等待啥甚至是誰阻塞它,阻塞者(罪魁禍首)是後臺進程還是前端SQL會話都可以看出來。後面要做的事情就是從gv$active_session_history 找到對應session_id 甚至對應sql_id 去解決。
col sample_time for a30 col event for a60 col count(1) for 999999 select to_char(trunc(sample_time,'mi'),'yyyy-mm-dd hh24:mi:ss') sample_time,event,count(1) from gv$active_session_history where sample_time >=to_date('&begin_time','yyyy-mm-dd hh24:mi:ss') and sample_time <=to_date('&end_time','yyyy-mm-dd hh24:mi:ss') group by trunc(sample_time,'mi'),event order by 1; --秒級別 select to_char(sample_time,'yyyy-mm-dd hh24:mi:ss') sample_time,event,count(1) from gv$active_session_history where sample_time >=to_date('&begin_time','yyyy-mm-dd hh24:mi:ss') and sample_time <=to_date('&end_time','yyyy-mm-dd hh24:mi:ss') group by to_char(sample_time,'yyyy-mm-dd hh24:mi:ss'),event order by 1;
腳本二:從v$active_session_history中查看某段時間阻塞鏈
with ash as (select inst_id,SESSION_ID,event,BLOCKING_SESSION,program , to_char(SAMPLE_TIME,'YYYYMMDD HH24MISS') SAMPLE_TIME,sample_id,blocking_inst_id from v$active_session_history where sample_time >to_date('2020-06-01 00:00:00','yyyy-mm-dd hh24:mi:ss') and to_char(sample_time,'hh24:mi:ss') between '06:00:00' and '06:10:00') select SAMPLE_TIME,FINAL_BLK,FINAL_PROGRAM, nvl(FINAL_EVT,'ON CPU') as FINAL_EVT ,(LVL2_EVENT) as LVL2_EVENT,LVL3_EVENT,LVL4_EVENT, count(*) from( select SESSION_ID,SAMPLE_TIME,sys_connect_by_path(SESSION_ID,',') CHAIN, connect_by_root(PROGRAM)as FINAL_PROGRAM, connect_by_root(SESSION_ID)FINAL_BLK, connect_by_root(event) FINAL_EVT,event , case when level=2 then event end as LVL2_EVENT , case when level=3 then event end as LVL3_EVENT , case when level=4 then event end as LVL4_EVENT from ash start with BLOCKING_SESSION is null connect by prior SESSION_ID = BLOCKING_SESSION and prior inst_id= BLOCKING_INST_ID and sample_id = prior sample_id ) group by FINAL_BLK,FINAL_EVT,SAMPLE_TIME,FINAL_PROGRAM ,LVL3_EVENT,LVL4_EVENT,LVL2_EVENT having count(*) > 1 order by SAMPLE_TIME,count(*) desc /
有時候通過上面快捷的腳本並沒有立馬找到問題根源,漏掉了一些關鍵細節信息。所以還需要進一步的分析原始的v$active_session_history信息,下面的章節步驟
通過ash逐步定位性能問題(詳細)
oracle 官方論壇給出的方法,可以清楚的看到如何通過ash一步步縮小問題範圍並定位問題,參考地址如下:
https://blogs.oracle.com/Database4CN/entry/如何通過dba_hist_active_sess_history分析歷史數據庫性能問題
Dump出問題期間的ASH數據:
爲了不影響生產系統,我們可以將問題大概期間的ASH數據export出來在測試機上分析。
基於dba_hist_active_sess_history創建一個新表m_ash,然後將其通過exp/imp導入到測試機。在發生問題的數據庫上執行exp:
SQL> conn user/passwd SQL> create table m_ash as select * from dba_hist_active_sess_history where SAMPLE_TIME between TO_TIMESTAMP ('<time_begin>', 'YYYY-MM-DD HH24:MI:SS') and TO_TIMESTAMP ('<time_end>', 'YYYY-MM-DD HH24:MI:SS');
$ exp user/passwd file=m_ash.dmp tables=(m_ash) log=m_ash.exp.log
然後導入到測試機:
$ imp user/passwd file=m_ash.dmp log=m_ash.imp.log
驗證導出的ASH時間範圍:
爲了加快速度,我們採用了並行查詢。另外建議採用Oracle SQL Developer來查詢以防止輸出結果折行不便於觀察。
set line 200 pages 1000 col sample_time for a25 col event for a40 alter session set nls_timestamp_format='yyyy-mm-dd hh24:mi:ss.ff'; select /*+ parallel 8 */ t.dbid, t.instance_number, min(sample_time), max(sample_time), count(*) session_count from m_ash t group by t.dbid, t.instance_number order by dbid, instance_number;
結果如下:
INSTANCE_NUMBER MIN(SAMPLE_TIME) MAX(SAMPLE_TIME) SESSION_COUNT 1 2015-03-26 21:00:04.278 2015-03-26 22:59:48.387 2171 2 2015-03-26 21:02:12.047 2015-03-26 22:59:42.584 36
從以上輸出可知該數據庫共2個節點,採樣時間共2小時,節點1的採樣比節點2要多很多,問題可能發生在節點1上。
確認問題發生的精確時間範圍:
參考如下腳本:
select /*+ parallel 8 */ dbid, instance_number, sample_id, sample_time, count(*) session_count from m_ash t group by dbid, instance_number, sample_id, sample_time order by dbid, instance_number, sample_time;
結果如下:
INSTANCE_NUMBER SAMPLE_ID SAMPLE_TIME SESSION_COUNT 1 36402900 2015-03-26 22:02:50.985 4 1 36402910 2015-03-26 22:03:01.095 1 1 36402920 2015-03-26 22:03:11.195 1 1 36402930 2015-03-26 22:03:21.966 21 1 36402940 2015-03-26 22:03:32.116 102 1 36402950 2015-03-26 22:03:42.226 181 1 36402960 2015-03-26 22:03:52.326 200 1 36402970 2015-03-26 22:04:02.446 227 1 36402980 2015-03-26 22:04:12.566 242 1 36402990 2015-03-26 22:04:22.666 259 1 36403000 2015-03-26 22:04:32.846 289 1 36403010 2015-03-26 22:04:42.966 147 1 36403020 2015-03-26 22:04:53.076 2 1 36403030 2015-03-26 22:05:03.186 4 1 36403040 2015-03-26 22:05:13.296 1 1 36403050 2015-03-26 22:05:23.398 1
注意觀察以上輸出的每個採樣點的active session的數量,數量突然變多往往意味着問題發生了。從以上輸出可以確定問題發生的精確時間在2015-03-26 22:03:21 ~ 22:04:42,問題持續了大約1.5分鐘。
注意: 觀察以上的輸出有無斷檔,比如某些時間沒有采樣。
確定每個採樣點的top n event:
在這裏我們指定的是top 2 event並且注掉了採樣時間以觀察所有采樣點的情況。如果數據量較多,您也可以通過開啓sample_time的註釋來觀察某個時間段的情況。注意最後一列session_count指的是該採樣點上的等待該event的session數量。
select t.dbid, t.sample_id, t.sample_time, t.instance_number, t.event, t.session_state, t.c session_count from (select t.*, rank() over(partition by dbid, instance_number, sample_time order by c desc) r from (select /*+ parallel 8 */ t.*, count(*) over(partition by dbid, instance_number, sample_time, event) c, row_number() over(partition by dbid, instance_number, sample_time, event order by 1) r1 from m_ash t /*where sample_time > to_timestamp('2013-11-17 13:59:00', 'yyyy-mm-dd hh24:mi:ss') and sample_time < to_timestamp('2013-11-17 14:10:00', 'yyyy-mm-dd hh24:mi:ss')*/ ) t where r1 = 1) t where r < 3 order by dbid, instance_number, sample_time, r;
結果如下:
SAMPLE_ID SAMPLE_TIME INSTANCE_NUMBER EVENT SESSION_STATE SESSION_COUNT 36402900 22:02:50.985 1 ON CPU 3 36402900 22:02:50.985 1 db file sequential read WAITING 1 36402910 22:03:01.095 1 ON CPU 1 36402920 22:03:11.195 1 db file parallel read WAITING 1 36402930 22:03:21.966 1 cursor: pin S wait on X WAITING 11 36402930 22:03:21.966 1 latch: shared pool WAITING 4 36402940 22:03:32.116 1 cursor: pin S wait on X WAITING 83 36402940 22:03:32.116 1 SGA: allocation forcing component growth WAITING 16 36402950 22:03:42.226 1 cursor: pin S wait on X WAITING 161 36402950 22:03:42.226 1 SGA: allocation forcing component growth WAITING 17 36402960 22:03:52.326 1 cursor: pin S wait on X WAITING 177 36402960 22:03:52.326 1 SGA: allocation forcing component growth WAITING 20 36402970 22:04:02.446 1 cursor: pin S wait on X WAITING 204 36402970 22:04:02.446 1 SGA: allocation forcing component growth WAITING 20 36402980 22:04:12.566 1 cursor: pin S wait on X WAITING 219 36402980 22:04:12.566 1 SGA: allocation forcing component growth WAITING 20 36402990 22:04:22.666 1 cursor: pin S wait on X WAITING 236 36402990 22:04:22.666 1 SGA: allocation forcing component growth WAITING 20 36403000 22:04:32.846 1 cursor: pin S wait on X WAITING 265 36403000 22:04:32.846 1 SGA: allocation forcing component growth WAITING 20 36403010 22:04:42.966 1 enq: US - contention WAITING 69 36403010 22:04:42.966 1 latch: row cache objects WAITING 56 36403020 22:04:53.076 1 db file scattered read WAITING 1 36403020 22:04:53.076 1 db file sequential read WAITING 1
從以上輸出我們可以發現問題期間最嚴重的等待爲cursor: pin S wait on X,高峯期等待該event的session數達到了265個,其次爲SGA: allocation forcing component growth,高峯期session爲20個。
注意:
- 再次確認以上輸出有無斷檔,是否有某些時間沒有采樣。
- 注意那些session_state爲ON CPU的輸出,比較ON CPU的進程個數與您的OS物理CPU的個數,如果接近或者超過物理CPU個數,那麼您還需要檢查OS當時的CPU資源狀況,比如OSWatcher/NMON等工具,高的CPU Run Queue可能引發該問題,當然也可能是問題的結果,需要結合OSWatcher和ASH的時間順序來驗證。
觀察每個採樣點的等待鏈:
其原理爲通過dba_hist_active_sess_history. blocking_session記錄的holder來通過connect by級聯查詢,找出最終的holder. 在RAC環境中,每個節點的ASH採樣的時間很多情況下並不是一致的,因此您可以通過將本SQL的第二段註釋的sample_time稍作修改讓不同節點相差1秒的採樣時間可以比較(注意最好也將partition by中的sample_time做相應修改)。該輸出中isleaf=1的都是最終holder,而iscycle=1的代表死鎖了(也就是在同一個採樣點中a等b,b等c,而c又等a,這種情況如果持續發生,那麼尤其值得關注)。採用如下查詢能觀察到阻塞鏈。
select /*+ parallel 8 */ level lv, connect_by_isleaf isleaf, connect_by_iscycle iscycle, t.dbid, t.sample_id, t.sample_time, t.instance_number, t.session_id, t.sql_id, t.session_type, t.event, t.session_state, t.blocking_inst_id, t.blocking_session, t.blocking_session_status from m_ash t /*where sample_time > to_timestamp('2013-11-17 13:55:00', 'yyyy-mm-dd hh24:mi:ss') and sample_time < to_timestamp('2013-11-17 14:10:00', 'yyyy-mm-dd hh24:mi:ss')*/ start with blocking_session is not null connect by nocycle prior dbid = dbid and prior sample_time = sample_time /*and ((prior sample_time) - sample_time between interval '-1' second and interval '1' second)*/ and prior blocking_inst_id = instance_number and prior blocking_session = session_id and prior blocking_session_serial# = session_serial# order siblings by dbid, sample_time;
結果如下:
LV ISLEAF ISCYCLE SAMPLE_TIME INSTANCE_NUMBER SESSION_ID SQL_ID EVENT SESSION_STATE BLOCKING_INST_ID BLOCKING_SESSION BLOCKING_SESSION_STATUS 1 0 0 22:04:32.846 1 1259 3ajt2htrmb83y cursor: WAITING 1 537 VALID 2 1 0 22:04:32.846 1 537 3ajt2htrmb83y SGA: WAITING UNKNOWN
注意,爲了輸出便於閱讀,我們將等待event做了簡寫,下同。從上面的輸出可見,在相同的採樣點上(22:04:32.846),節點1 session 1259在等待cursor: pin S wait on X,其被節點1 session 537阻塞,而節點1 session 537又在等待SGA: allocation forcing component growth,並且ASH沒有采集到其holder,因此這裏cursor: pin S wait on X只是一個表面現象,問題的原因在於SGA: allocation forcing component growth
基於第5步的原理來找出每個採樣點的最終top holder:
比如如下SQL列出了每個採樣點top 2的blocker session,並且計算了其最終阻塞的session數(參考blocking_session_count)
select t.lv, t.iscycle, t.dbid, t.sample_id, t.sample_time, t.instance_number, t.session_id, t.sql_id, t.session_type, t.event, t.seq#, t.session_state, t.blocking_inst_id, t.blocking_session, t.blocking_session_status, t.c blocking_session_count from (select t.*, row_number() over(partition by dbid, instance_number, sample_time order by c desc) r from (select t.*, count(*) over(partition by dbid, instance_number, sample_time, session_id) c, row_number() over(partition by dbid, instance_number, sample_time, session_id order by 1) r1 from (select /*+ parallel 8 */ level lv, connect_by_isleaf isleaf, connect_by_iscycle iscycle, t.* from m_ash t /*where sample_time > to_timestamp('2013-11-17 13:55:00', 'yyyy-mm-dd hh24:mi:ss') and sample_time < to_timestamp('2013-11-17 14:10:00', 'yyyy-mm-dd hh24:mi:ss')*/ start with blocking_session is not null connect by nocycle prior dbid = dbid and prior sample_time = sample_time /*and ((prior sample_time) - sample_time between interval '-1' second and interval '1' second)*/ and prior blocking_inst_id = instance_number and prior blocking_session = session_id and prior blocking_session_serial# =session_serial#) t where t.isleaf = 1) t where r1 = 1) t where r < 3 order by dbid, sample_time, r;
結果如下:
SAMPLE_TIME INSTANCE_NUMBER SESSION_ID SQL_ID EVENT SEQ# SESSION_STATE BLOCKING_SESSION_STATUS BLOCKING_SESSION_COUNT 22:03:32.116 1 1136 1p4vyw2jan43d SGA: 1140 WAITING UNKNOWN 82 22:03:32.116 1 413 9g51p4bt1n7kz SGA: 7646 WAITING UNKNOWN 2 22:03:42.226 1 1136 1p4vyw2jan43d SGA: 1645 WAITING UNKNOWN 154 22:03:42.226 1 537 3ajt2htrmb83y SGA: 48412 WAITING UNKNOWN 4 22:03:52.326 1 1136 1p4vyw2jan43d SGA: 2150 WAITING UNKNOWN 165 22:03:52.326 1 537 3ajt2htrmb83y SGA: 48917 WAITING UNKNOWN 8 22:04:02.446 1 1136 1p4vyw2jan43d SGA: 2656 WAITING UNKNOWN 184 22:04:02.446 1 537 3ajt2htrmb83y SGA: 49423 WAITING UNKNOWN 10 22:04:12.566 1 1136 1p4vyw2jan43d SGA: 3162 WAITING UNKNOWN 187 22:04:12.566 1 2472 SGA: 1421 WAITING UNKNOWN 15 22:04:22.666 1 1136 1p4vyw2jan43d SGA: 3667 WAITING UNKNOWN 193 22:04:22.666 1 2472 SGA: 1926 WAITING UNKNOWN 25 22:04:32.846 1 1136 1p4vyw2jan43d SGA: 4176 WAITING UNKNOWN 196 22:04:32.846 1 2472 SGA: 2434 WAITING UNKNOWN 48
注意以上輸出,比如第一行,代表在22:03:32.116,節點1的session 1136最終阻塞了82個session. 順着時間往下看,可見節點1的session 1136是問題期間最嚴重的holder,它在每個採樣點都阻塞了100多個session,並且它持續等待SGA: allocation forcing component growth,注意觀察其seq#您會發現該event的seq#在不斷變化,表明該session並未完全hang住,由於時間正好發生在夜間22:00左右,這顯然是由於自動收集統計信息job導致shared memory resize造成,因此可以結合Scheduler/MMAN的trace以及dba_hist_memory_resize_ops的輸出進一步確定問題。
注意:
- blocking_session_count 指某一個holder最終阻塞的session數,比如 a <- b<- c (a被b阻塞,b又被c阻塞),只計算c阻塞了1個session,因爲中間的b可能在不同的阻塞鏈中發生重複。
- 如果最終的holder沒有被ash採樣(一般因爲該holder處於空閒),比如 a<- c 並且b<- c (a被c阻塞,並且b也被c阻塞),但是c沒有采樣,那麼以上腳本無法將c統計到最終holder裏,這可能會導致一些遺漏。
- 注意比較blocking_session_count的數量與第3步查詢的每個採樣點的總session_count數,如果每個採樣點的blocking_session_count數遠小於總session_count數,那表明大部分session並未記載holder,因此本查詢的結果並不能代表什麼。
- 在Oracle 10g中,ASH並沒有blocking_inst_id列,在以上所有的腳本中,您只需要去掉該列即可,因此10g的ASH一般只能用於診斷單節點的問題。
其他關於ASH的應用
除了通過ASH數據來找holder以外,我們還能用它來獲取很多信息(基於數據庫版本有所不同):
比如通過PGA_ALLOCATED列來計算每個採樣點的最大PGA,合計PGA以分析ora-4030/Memory Swap相關問題;
通過TEMP_SPACE_ALLOCATED列來分析臨時表空間使用情況;
通過IN_PARSE/IN_HARD_PARSE/IN_SQL_EXECUTION列來分析SQL處於parse還是執行階段;
通過CURRENT_OBJ#/CURRENT_FILE#/CURRENT_BLOCK#來確定I/O相關等待發生的對象.