【DB筆試面試676】在Oracle中,一個RAC雙節點的實例環境…給EMP表加鎖:請嘗試解決這個故障。
- 2019 年 11 月 5 日
- 筆記
題目部分
在Oracle中,一個RAC雙節點的實例環境,面試人員使用的是實例2,而在實例1中已經使用「SELECT * FROM SCOTT.EMP FOR UPDATE;」給EMP表加鎖:
SQL> SELECT * FROM SCOTT.EMP FOR UPDATE;
此時在實例2中,如果執行以下SQL語句嘗試更新ENAME欄位,那麼必然會被行鎖堵塞:
SQL> UPDATE SCOTT.EMP SET ENAME='ENMOTECH' WHERE EMPNO=7369;
請嘗試解決這個故障。
♣
答案部分
這道面試題中包含的知識點有:
① 如何在另外一個SESSION中查找被堵塞的SESSION資訊;
② 如何找到產生行鎖的BLOCKER;
③ 在殺掉BLOCKER進程之前會不會向面試監考人員詢問,是否可以KILL掉阻塞者;
④ 在獲得可以KILL掉進程的確認回復後,正確殺掉另一個實例上的進程。
正確的思路和解法應該如下:
(1)檢查被阻塞會話的等待事件
更新語句回車以後沒有回顯,明顯是被阻塞了,那麼現在這個會話當前是什麼等待事件呢?可以通過SESSION等待去獲取這些資訊:
SQL> SELECT SID,EVENT,USERNAME,SQL.SQL_TEXT FROM V$SESSION S,V$SQL SQL WHERE S.SQL_ID=SQL.SQL_ID AND SQL.SQL_TEXT LIKE 'UPDATE SCOTT.EMP SET ENAME%'; SID EVENT USERNAME SQL_TEXT ---------- ---------------------------------- ----------- ---------------------------------------------------------------- 31 enq: TX - row lock contention SYS UPDATE SCOTT.EMP SET ENAME='ENMOTECH' WHERE EMPNO=7369
說明被阻塞的會話SID為31。以上使用的是關聯V$SQL的SQL語句,實際上通過登錄用戶名等也可以快速定位被鎖住的會話。
(2)查找 BLOCKER
得知等待事件是「enq: TX – row lock contention」,行鎖,接下來就是要找到誰鎖住了這個會話。在Oracle 10gR2以後,只需要查詢GV$SESSION視圖就可以迅速定位BLOCKER,通過BLOCKING_INSTANCE和BLOCKING_SESSION欄位即可。
SQL> SELECT SID,INST_ID,BLOCKING_INSTANCE,BLOCKING_SESSION FROM gv$session WHERE INST_ID=2 AND SID=31; SID INST_ID BLOCKING_INSTANCE BLOCKING_SESSION ---------- ---------- ----------------- ---------------- 31 2 1 65 SQL> SELECT SID,EVENT,S.USERNAME,SQL.SQL_TEXT FROM GV$SESSION S,GV$SQL SQL WHERE (S.SQL_ID=SQL.SQL_ID OR S.PREV_SQL_ID=SQL.SQL_ID) AND S.INST_ID=SQL.INST_ID AND SID=65; SID EVENT USERNAME SQL_TEXT ---------- ------------------------------ --------- ------------------------------------- 65 SQL*Net message from client LHR SELECT * FROM SCOTT.EMP FOR UPDATE
可以看到,實例1上的SID為65的會話阻塞了實例2上的SID為31的會話,並且SQL語句為「SELECT * FROM SCOTT.EMP FOR UPDATE」。上述方法是最簡單的,如果使用更傳統的方法,那麼實際上也並不難,從GV$LOCK視圖中去查詢即可,如下所示:
SQL> SQL> SELECT TYPE,ID1,ID2,LMODE,REQUEST FROM gv$lock WHERE sid=31 and INST_ID=2; TY ID1 ID2 LMODE REQUEST -- ---------- ---------- ---------- ---------- AE 100 0 4 0 TX 262148 1512 0 6 TM 73201 0 3 0 SQL> SELECT INST_ID,SID,TYPE,LMODE,REQUEST FROM gv$lock WHERE ID1=262148 and ID2=1512; INST_ID SID TY LMODE REQUEST ---------- ---------- -- ---------- ---------- 2 31 TX 0 6 1 65 TX 6 0
說明是實例1上的SID為65的會話阻塞了實例2上的SID為31的會話。
(3)乙方DBA需謹慎
第三個知識點是考核作為乙方的謹慎,即使查到了BLOCKER,是不是應該直接KILL掉,必須要先徵詢客戶的意見,確認之後才可以殺掉。
(4)清除BLOCKER
已經確認了可以KILL掉SESSION之後,需要再找到相應SESSION的SERAIL#,這是KILL SESSION時必須輸入的參數。
SQL> SELECT SID,SERIAL# FROM gv$session WHERE INST_ID=1 AND SID=65; SID SERIAL# ---------- ---------- 65 3707
如果是 11gR2 資料庫,那麼直接在實例2中加入@1參數就可以殺掉實例1中的會話,如果是10g,那麼登入實例1再執行KILL SESSION的操作。
SQL> ALTER SYSTEM KILL SESSION '65,3707,@1' IMMEDIATE; System altered.
再檢查之前被阻塞的更新會話,可以看到已經更新成功了。
SQL> UPDATE SCOTT.EMP SET ENAME='ENMOTECH' WHERE EMPNO=7369; 1 row updated.
(5)延伸內容
在該問題中,面試官可能會再延伸的詢問以下問題:
① 在V$LOCK視圖中顯示的ID1和ID2是什麼意思?
答:在V$LOCK中,當TYPE列的值為TM鎖時,則ID1列的值為DBA_OBJECTS.OBJECT_ID,ID2列的值為0;當TYPE列的值為TX鎖時,則ID1列的值為視圖V$TRANSACTION中的XIDUSN欄位(Undo Segment Number,事務對應的撤銷段序列號)和XIDSLOT欄位(Slot Number,事務對應的槽位號),其中,ID1的高16位為XIDUSN,低16位為XIDSLOT。ID2列的值為視圖V$TRANSACTION中的XIDSQN欄位(Sequence Number:事務對應的序列號)。
當TYPE列的值為TX鎖時,計算ID1列的值的公式為:
SELECT TRUNC(ID1/POWER(2,16)) AS XIDUSN,BITAND(ID1,TO_NUMBER('FFFF','XXXX')) + 0 AS XIDSLOT , ID2 XIDSQN FROM DUAL;
所有與鎖有關的數據字典視圖之間的關聯關係如下圖所示:

② 在使用KILL命令殺會話的時候加上IMMEDIATE和不加IMMEDIATE的區別是什麼?KILLED狀態的會話如何刪除其OS進程?
答:一般情況下,可以通過執行SQL語句「ALTER SYSTEM KILL SESSION 'SID,SERIAL#'」直接殺掉會話。當SESSION是ACTIVE的時候,ALTER SYSTEM KILL SESSION只是將SESSION的狀態標識為KILLED,SERVER變為PSEUDO狀態,但可能並不會立即釋放SESSION所有所持有的資源,所以,在執行完ALTER SYSTEM KILL SESSION後,會話還是一直存在(V$SESSION視圖中存在,且後邊OS進程也存在)。所以,在執行命令KILL SESSION的時候,可以在後邊加上IMMEDIATE,這樣在沒有事務的情況下,相關會話就會立即被刪除而不會變為KILLED的狀態(V$SESSION視圖中不存在),當有事務存在的情況下,會先進行回滾相關的事務,然後釋放會話所佔有的資源。
另外,由於變為KILLED狀態的會話的PADDR列都變成了另外一個值,因此,通過平常的連接方式就沒有辦法關聯到後台進程,在Oracle 11g下提供了CREATOR_ADDR列,該列可以關聯到後台進程,對於Oracle 10g可以通過特殊的SQL找到後台的進程號。
Oracle 10g可以使用如下的腳本:
SELECT INST_ID, SPID, PROGRAM,'KILL -9 '|| SPID KILL9 FROM GV$PROCESS A WHERE PROGRAM != 'PSEUDO' AND (INST_ID, ADDR) NOT IN (SELECT INST_ID, PADDR FROM GV$SESSION) AND (INST_ID, ADDR) NOT IN (SELECT INST_ID, PADDR FROM GV$BGPROCESS) AND (INST_ID, ADDR) NOT IN (SELECT INST_ID, PADDR FROM GV$SHARED_SERVER) ;
Oracle 11g可以使用如下的腳本:
SET LINE 9999 COL SESSIONID FORMAT A20 COL SESSIONID_KILLED FORMAT A20 COL KILL_SESSION FORMAT A60 SELECT A.INST_ID, A.SID || ',' || A.SERIAL# || ',' || (SELECT SPID FROM GV$PROCESS B WHERE B.INST_ID = A.INST_ID AND A.CREATOR_ADDR = B.ADDR --AND DECODE(A.STATUS,'KILLED',A.CREATOR_ADDR,A.PADDR) = B.ADDR ) SESSIONID, A.PADDR, A.STATUS, A.PROGRAM, 'ALTER SYSTEM DISCONNECT SESSION ''' || SID || ',' || SERIAL# || ''' IMMEDIATE;' KILL_SESSION FROM GV$SESSION A WHERE A.USERNAME = 'SYS' AND A.STATUS = 'KILLED';
本文選自《Oracle程式設計師面試筆試寶典》,作者:小麥苗