阻塞是DBA經常碰到的情形,尤其是不良的應用程序設計的阻塞將導致性能嚴重下降直至數據庫崩潰。對DBA而言,有必要知道如何定位到當前系統有哪些阻塞,到底誰是阻塞者,誰是被阻塞者。本文對此給出了描述並做了相關演示。
1、阻塞及其類型
a、什麼是阻塞
一個會話持有某個資源的鎖,而另一個會話在請求這個資源,就會出現阻塞(blocking)。也就是說新的會話會被掛起,直到持有鎖的會話放棄鎖定的資源。大多數情況下,在一個交互式應用中被嚴重阻塞,即可表明應用邏輯有問題,這才是阻塞的根源。
b、阻塞得類型
數據庫中有5條常見的DML語句可能會阻塞,即:INSERT、UPDATE、DELETE、MERGE 和SELECT FOR UPDATE。
2、幾種不同類型阻塞的處理辦法
a、INSERT阻塞主要是由於有一個帶主鍵的表,或者表上有惟一的約束,在兩個會話試圖用同樣的值插入一行時引發阻塞。多表通過引用完整性約束相互鏈接時,在其依賴的父表正在創建或刪除期間,對子表的插入可能會阻塞。對於該類情形建議使用序列來生成主鍵/惟一列值。
b、對於UPDATE、DELETE、MERGE 和SELECT FOR UPDATE阻塞,只要有任一session使用這些操作已經鎖定行,其余的必須處於等待狀態。直到當前鎖定行上的鎖(排他鎖)釋放。對於該類情形,建議盡可能快速提交事務,或采用批量SQL方式提交。
c、對於一個阻塞的SELECT FOR UPDATE,解決方案很簡單:只需增加NOWAIT 子句,它就不會阻塞了。
3、演示阻塞
--更新表,注,提示符scott@CNMMBO表明用戶為scott的session,用戶名不同,session不同。 scott@CNMMBO> update emp set sal=sal*1.1 where empno=7788; 1 row updated. scott@CNMMBO> @my_env SPID SID SERIAL# USERNAME PROGRAM ------------ ---------- ---------- --------------- ------------------------------------------------ 11205 1073 4642 robin oracle@SZDB (TNS V1-V3) --另起兩個session更新同樣的行,這兩個session都會處於等待,直到第一個session提交或回滾 leshami@CNMMBO> update scott.emp set sal=sal+100 where empno=7788; goex_admin@CNMMBO> update scott.emp set sal=sal-50 where empno=7788; --下面在第一個session 查詢阻塞情況 scott@CNMMBO> @blocker BLOCK_MSG BLOCK -------------------------------------------------- ---------- pts/5 ('1073,4642') is blocking 1067,10438 1 pts/5 ('1073,4642') is blocking 1065,4464 1 --上面的結果表明session 1073,4642 阻塞了後面的2個 --即session 1073,4642是阻塞者,後面2個session是被阻塞者 --Author : Leshami --Blog : http://blog.csdn.net/leshami --下面查詢正在阻塞的session id,SQL語句以及被阻塞的時間 scott@CNMMBO> @blocking_session_detail.sql 'SID='||A.SID||'WAITCLASS='||A.WAIT_CLASS||'TIME='||A.SECONDS_IN_WAIT||CHR(10)||'QUERY='||B.SQL_TEXT ------------------------------------------------------------------------------------------------------- sid=1067 Wait Class=Application Time=5995 Query=update scott.emp set sal=sal+100 where empno=7788 sid=1065 Wait Class=Application Time=225 Query=update scott.emp set sal=sal-50 where empno=7788 --下面的查詢阻塞時鎖的持有情況 scott@CNMMBO> @request_lock_type USERNAME SID TY LMODE REQUEST ID1 ID2 ------------------------------ ---------- -- ----------- ----------- ---------- ---------- SCOTT 1073 TX Exclusive None 524319 27412 LESHAMI 1067 TX None Exclusive 524319 27412 GOEX_ADMIN 1065 TX None Exclusive 524319 27412 --可以看到LESHAMI,GOEX_ADMIN 2個用戶都在請求524319/27412上的Exclusive鎖,而此時已經被SCOTT加了Exclusive鎖 --查詢阻塞時鎖的持有詳細信息 scott@CNMMBO> @request_lock_detail SID USERNAME OSUSER TERMINAL OBJECT_NAME TY Lock Mode Req_Mode ---------- -------------------- --------------- ------------------------- -------------------- -- ----------- -------------------- 1065 GOEX_ADMIN robin pts/1 EMP TM Row Excl 1065 GOEX_ADMIN robin pts/1 Trans-524319 TX --Waiting-- Exclusive 1067 LESHAMI robin pts/0 EMP TM Row Excl 1067 LESHAMI robin pts/0 Trans-524319 TX --Waiting-- Exclusive 1073 SCOTT robin pts/5 EMP TM Row Excl 1073 SCOTT robin pts/5 Trans-524319 TX Exclusive
文中涉及到的相關腳本如下:
robin@SZDB:~/dba_scripts/custom/sql> more my_env.sql SELECT spid, s.sid, s.serial#, p.username, p.program FROM v$process p, v$session s WHERE p.addr = s.paddr AND s.sid = (SELECT sid FROM v$mystat WHERE rownum = 1); robin@SZDB:~/dba_scripts/custom/sql> more blocker.sql col block_msg format a50; select c.terminal||' ('''||a.sid||','||c.serial#||''') is blocking '||b.sid||','||d.serial# block_msg, a.block from v$lock a,v$lock b,v$session c,v$session d where a.id1=b.id1 and a.id2=b.id2 and a.block>0 and a.sid <>b.sid and a.sid=c.sid and b.sid=d.SID; robin@SZDB:~/dba_scripts/custom/sql> more blocking_session_detail.sql --To find the query for blocking session --Access Privileges: SELECT on v$session, v$sqlarea SELECT 'sid=' || a.SID || ' Wait Class=' || a.wait_class || ' Time=' || a.seconds_in_wait || CHR (10) || ' Query=' || b.sql_text FROM v$session a, v$sqlarea b WHERE a.blocking_session IS NOT NULL AND a.sql_address = b.address ORDER BY a.blocking_session / robin@SZDB:~/dba_scripts/custom/sql> more request_lock_type.sql --This script generates a report of users waiting for locks. --Access Privileges: SELECT on v$session, v$lock SELECT sn.username, m.sid, m.type, DECODE(m.lmode, 0, 'None', 1, 'Null', 2, 'Row Share', 3, 'Row Excl.', 4, 'Share', 5, 'S/Row Excl.', 6, 'Exclusive', lmode, ltrim(to_char(lmode,'990'))) lmode, DECODE(m.request,0, 'None', 1, 'Null', 2, 'Row Share', 3, 'Row Excl.', 4, 'Share', 5, 'S/Row Excl.', 6, 'Exclusive', request, ltrim(to_char(m.request, '990'))) request, m.id1, m.id2 FROM v$session sn, v$lock m WHERE (sn.sid = m.sid AND m.request != 0) OR (sn.sid = m.sid AND m.request = 0 AND lmode != 4 AND (id1, id2) IN (SELECT s.id1, s.id2 FROM v$lock s WHERE request != 0 AND s.id1 = m.id1 AND s.id2 = m.id2) ) ORDER BY id1, id2, m.request; robin@SZDB:~/dba_scripts/custom/sql> more request_lock_detail.sql set linesize 190 col osuser format a15 col username format a20 wrap col object_name format a20 wrap col terminal format a25 wrap col Req_Mode format a20 select B.SID, C.USERNAME, C.OSUSER, C.TERMINAL, DECODE(B.ID2, 0, A.OBJECT_NAME, 'Trans-'||to_char(B.ID1)) OBJECT_NAME, B.TYPE, DECODE(B.LMODE,0,'--Waiting--', 1,'Null', 2,'Row Share', 3,'Row Excl', 4,'Share', 5,'Sha Row Exc', 6,'Exclusive', 'Other') "Lock Mode", DECODE(B.REQUEST,0,' ', 1,'Null', 2,'Row Share', 3,'Row Excl', 4,'Share', 5,'Sha Row Exc', 6,'Exclusive', 'Other') "Req_Mode" from DBA_OBJECTS A, V$LOCK B, V$SESSION C where A.OBJECT_ID(+) = B.ID1 and B.SID = C.SID and C.USERNAME is not null order by B.SID, B.ID2;
Oracle數據庫運維過程中有時會遇到一種異常情況,由於錯誤的操作或代碼BUG造成session異常地持有鎖不釋放,並大量阻塞系統對話。這時候需要找出造成異常阻塞的session並清除。
oracle session通常具有三個特征:
(1)一個session可能阻塞多個session;
(2)一個session最多被一個session阻塞;
(3)session阻塞關系不會形成環路。(環路即死鎖,oracle能自動解除)
因此session的阻塞關系為一棵樹,進而DB系統所有session的BLOCK阻塞關系是一個由若干session阻塞關系樹構成的森林,而異常session一定會在故障爆發時成為根(root)。因此,找尋異常鎖表session的過程就是找出異常的root。
一般認為異常root有兩個特征:(1)block樹的規模過大,阻塞樹規模即被root層層阻塞的session總數;(2)阻塞的平均等待時間過長。
查找異常session的方法一:
OEM—> performance—> Blocking Sessions
查找異常session的方法二:
select r.root_sid, s.serial#,
r.blocked_num, r.avg_wait_seconds,
s.username,s.status,s.event,s.MACHINE,
s.PROGRAM,s.sql_id,s.prev_sql_id
from (select root_sid, avg(seconds_in_wait) as avg_wait_seconds,
count(*) - 1 as blocked_num
from (select CONNECT_BY_ROOT sid as root_sid, seconds_in_wait
from v$session
start with blocking_session is null
connect by prior sid = blocking_session)
group by root_sid
having count(*) > 1) r,
v$session s
where r.root_sid = s.sid
order by r.blocked_num desc, r.avg_wait_seconds desc;
該SQL語句即是根據v$session的字段blocking_session統計阻塞樹根阻塞session的計數以及平均阻塞時間、並進行排序,排名最前的往往是異常session。
另外需要注意的是,持有鎖時間最長、或等待時間最長的session都不一定是造成阻塞的根源session!
、行級鎖
insert ,update ,delete,自動在行上加一個行級鎖。通過commit,rollback解鎖。
查看行級鎖阻塞情況:
select sid , blocking_session,username,event from v$session where blocking_session_status='VALID';
查看session加鎖情況,不含鎖阻塞的情況:
select * from v$locked_objects;
oracle 加鎖是依次執行的,假設有3個用戶,同時修改某行數據。第一個用戶先獲得行級鎖,其他兩個用戶處於等待狀態,也就是阻塞了其他兩個用戶。當這個用戶 commit操作,解放自身的行級鎖。此時,第二個用戶將獲得行級鎖,再阻塞第三個用戶。直到第二個用戶釋放行級鎖,第三個用戶才有機會獲得鎖,否則將一直處於等待狀態。
2、表級鎖
1) lock table tab_name in share mode(共享鎖) 在表級別上加上共享鎖,在該表上用戶只能夠select操作,其他操作都被阻塞,要一直等到該表的鎖釋放。同時還允許其他用戶在該表上也加上share鎖。Type為4。
解鎖:用戶在該表上執行commit或rollback後,將解除該鎖。
2) lock table tab_name in exclusive mode(排他鎖) 在表級別上加上排他鎖,在該表上用戶只能select操作,其他操作都被阻塞,通過還不允許用戶再在該表上加上其他類型的鎖。Type為6(加鎖級別最高)。
解鎖:用戶在該表上執行commit或rollback後,將解除該鎖。
3) Lock table tab_name in share row exclusive mode():在表級別上加上排他鎖,在該表上用戶只能select操作,其他操作將被阻塞,不能加共享鎖、共享行排他鎖和排他鎖。Type為5。
解鎖:用戶在該表上執行commit或rollback後,將解除該鎖。
3、數據庫級鎖
1) alter system enable restricted session;
受限方式打開數據庫,對已經連接上數據庫的用戶仍然能夠操作數據庫;要連接數據庫,用戶必須具有restrictive session的權限。
其目的主要是希望在數據庫打開期間,且在沒有用戶會話干擾(如建立連接和執行任務)的情況下完成數據庫的維護操作(如數據庫的導入、導出)。
Alter system disable restricted session;解除數據庫受限模式。
2) 以只讀方式打開數據庫
Shutdown immediate
Startup mount
Alter database open read only;
這時候數據庫只能夠執行select操作,其他操作就要拋出錯。