Oracle關於TX鎖的一個有趣的問題

前陣子有一個網友在羣裏問了一個關於Oracle數據庫的TX鎖問題,問題原文以下:數據庫

 

請教一個問題: 兩個會話執行不一樣的delete語句,結果都是刪除同一個行。先執行的會話裏where條件不加索引走全表掃描,表很大,執行很慢;後執行的用where條件直接用rowid進行delete Oracle的什麼機制使第二個會話執行後一直是等待第一個會話結束的呢。session

 

那麼咱們先動手實驗一下,來看看這個問題吧,首先,咱們須要一個數據量較大的表(數據量大,全表掃描時間長,方便構造實驗效果), 這裏實驗測試的表爲INV_TEST,該表在字段FINAL_GARMENT_FACTORY_CD上沒有索引。由於咱們要構造一個SQL走全表掃描去刪除數據。咱們更新了兩條記錄,設置字段FINAL_GARMENT_FACTORY_CD ='KLB'。 以下所示:oracle

 

SQL> SELECT  ROWID, T.FINAL_GARMENT_FACTORY_CD FROM TEST.INV_TEST T WHERE ROWNUM <=10;
 
ROWID              FINAL_GARM
------------------ ----------
AAC1coABNAAALEKAAA KLB
AAC1coABNAAALEKAAB GEG
AAC1coABNAAALEKAAC GEG
AAC1coABNAAALEKAAD GEG
AAC1coABNAAALEKAAE GEG
AAC1coABNAAALEKAAF KLB
AAC1coABNAAALEKAAG GEG
AAC1coABNAAALEKAAH GEG
AAC1coABNAAALEKAAI GEG
AAC1coABNAAALEKAAJ GEG

 

首先,在會話1SID=925)裏面執行下面SQL語句,刪除FINAL_GARMENT_FACTORY_CD ='KLB'的兩條記錄app

 

SQL> SELECT USERENV('SID') FROM DUAL;
 
USERENV('SID')
--------------
           925
 
SQL> DELETE FROM TEST.INV_TEST WHERE FINAL_GARMENT_FACTORY_CD ='KLB';

 

在會話1SID=925)執行後,咱們在會話2SID=197)裏面執行一個DELETE語句(刪除ROWID ='AAC1coABNAAALEKAAA'的記錄),其實就是刪除第一條FINAL_GARMENT_FACTORY_CD ='KLB'的記錄。不過咱們使用的是ROWID這個條件。dom

 

 

 
SQL> SELECT USERENV('SID') FROM DUAL;                                     
 
USERENV('SID')
--------------
           917
 
SQL> DELETE FROM TEST.INV_TEST WHERE ROWID ='AAC1coABNAAALEKAAA';

 

 

此時,在會話3,咱們使用下面SQL語句查詢,就會發現會話2SID=917)被會話1SID=925)阻塞了。測試

 

 

SQL> COLUMN blockeduser FORMAT a30 
SQL> SET linesize 480
SQL> BREAK ON BlockingInst SKIP 1 ON BlockingSid skip 1 ON BlockingSerial SKIP 1 
SQL> SELECT DISTINCT s1.INST_ID         BlockingInst, 
  2                  s1.SID             BlockingSid, 
  3                  s1.SERIAL#         BlockingSerial, 
  4                  s2.INST_ID         BlockedInst, 
  5                  s2.SID             BlockedSid, 
  6                  s2.USERNAME        BlockedUser, 
  7                  s2.SECONDS_IN_WAIT BlockedWaitTime 
  8  FROM   gv$session s1, 
  9         gv$lock l1, 
 10         gv$session s2, 
 11         gv$lock l2 
 12  WHERE  s1.INST_ID = l1.INST_ID 
 13         AND l1.BLOCK IN ( 1, 2 ) 
 14         AND l2.REQUEST != 0 
 15         AND l1.SID = s1.SID 
 16         AND l1.ID1 = l2.ID1 
 17         AND l1.ID2 = l2.ID2 
 18         AND s2.SID = l2.SID 
 19         AND s2.INST_ID = l2.INST_ID 
 20  ORDER  BY 1, 
 21            2, 
 22            3 
 23  / 
 
BLOCKINGINST BLOCKINGSID BLOCKINGSERIAL BLOCKEDINST BLOCKEDSID BLOCKEDUSER  BLOCKEDWAITTIME
------------ ----------- -------------- ----------- ---------- ------------ ---------------
           1         925          11600           1        917 TEST         30

 

 

SQL> COL SID  FOR 999999;
SQL> COL USERNAME FOR A12;
SQL> COL MACHINE FOR A40;
SQL> COL TYPE FOR A10;
SQL> COL OBJECT_NAME FOR A32;
SQL> COL LMODE FOR A16;
SQL> COL REQUEST FOR A12;
SQL> COL BLOCK FOR 999999;
SQL> SELECT S.SID                             SID, 
  2         S.USERNAME                        USERNAME, 
  3         S.MACHINE                         MACHINE, 
  4         L.TYPE                            TYPE, 
  5         O.OBJECT_NAME                     OBJECT_NAME, 
  6         DECODE(L.LMODE, 0, 'None', 
  7                         1, 'Null', 
  8                         2, 'Row Share', 
  9                         3, 'Row Exlusive', 
 10                         4, 'Share', 
 11                         5, 'Sh/Row Exlusive', 
 12                         6, 'Exclusive')   LMODE, 
 13         DECODE(L.REQUEST, 0, 'None', 
 14                           1, 'Null', 
 15                           2, 'Row Share', 
 16                           3, 'Row Exlusive', 
 17                           4, 'Share', 
 18                           5, 'Sh/Row Exlusive', 
 19                           6, 'Exclusive') REQUEST, 
 20         L.BLOCK                           BLOCK 
 21  FROM   V$LOCK L, 
 22         V$SESSION S, 
 23         DBA_OBJECTS O 
 24  WHERE  L.SID = S.SID 
 25         AND USERNAME != 'SYSTEM' 
 26         AND O.OBJECT_ID(+) = L.ID1; 
 
    SID USERNAME     MACHINE                TYPE       OBJECT_NAME      LMODE            REQUEST   BLOCK
------- ------------ ------------------ ---------- ---------------- ---------------- ------------ -------
    917 TEST    DB-Server.localdomain      TM         INV_TEST         Row Exlusive     None          0
    925 TEST    DB-Server.localdomain      TM         INV_TEST         Row Exlusive     None          0
    925 TEST    DB-Server.localdomain      TX                          Exclusive        None          1
    917 TEST    DB-Server.localdomain      TX                          None             Exclusive     0

 

 

使用下面腳本,咱們知道,會話197ROW_ID=AAC1coABNAAALEKAAA 這條記錄上等待獲取TX鎖,從而致使他被阻塞了。spa

 

 

COL object_name FOR A32;
COL row_id FOR A32;
SELECT
     s.p1raw,
     o.owner,
     o.object_name,
     dbms_rowid.rowid_create(1,o.data_object_id,f.relative_fno,s.row_wait_block#,s.row_wait_row#) row_id
 FROM
     v$session s
     JOIN dba_objects o ON s.row_wait_obj# = o.object_id
     JOIN dba_segments m ON o.owner = m.owner
                            AND o.object_name = m.segment_name
     JOIN dba_data_files f ON s.row_wait_file# = f.file_id
                              AND m.tablespace_name = f.tablespace_name
 WHERE
     s.event LIKE 'enq: TX%'

 

 

clip_image001

 

 

 

其實到這裏就能夠回答以前網友的問題了。 其實很簡單,就是ORACLE數據庫的鎖機制實現的。咱們知道TX鎖稱爲事務鎖或行級鎖。當Oracle執行DML語句時,系統自動在所要操做的表上申請TM類型的鎖。當TM鎖得到後,系統再自動申請TX類型的鎖,並將實際鎖定的數據行的鎖標誌位進行置位。code

 

在數據行上只有X鎖(排他鎖)。在 Oracle數據庫中,當一個事務首次發起一個DML語句時就得到一個TX鎖,該鎖保持到事務被提交或回滾。當兩個或多個會話在表的同一條記錄上執行 DML語句時,第一個會話在該條記錄上加鎖,其餘的會話處於等待狀態。當第一個會話提交後,TX鎖被釋放,其餘會話才能夠加鎖。因爲第一個SQL語句的執行計劃走全表掃描,因此致使這個事務的時間很長,會話2就一直被阻塞,直到第一個會話提交或回滾。orm

 

另外,咱們都知道在Oracle中實現了細粒度的行鎖row lock,且在ORACLE的內部實現中沒有使用基於內存的行鎖管理器,row lock是依賴於數據塊自己實現的。換句話說斷定一行數據究竟有沒有沒鎖住,要求Server Processpin住相應的block buffer並檢查纔可以發現。因此,對於會話1SID=925),咱們沒法定位到那些行獲取了TX鎖。這個能夠參考https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9533876300346704362blog

 

那麼問題來了,對於會話1SQL走全表掃描,找到FINAL_GARMENT_FACTORY_CD ='KLB'的記錄就會在對應的數據行的鎖標誌進行置位。假如FINAL_GARMENT_FACTORY_CD ='KLB'的記錄位於掃描位置的末端呢? 這個實驗會是什麼樣的結果呢?咱們用下面SQL找出一些記錄。

 

SELECT ROWID, T.* FROM INV_TEST T WHERE STOCK_DATE > SYSDATE -120

 

而後咱們將其中一條記錄使用下面腳本更新。

 

SQL> UPDATE INV_TEST SET FINAL_GARMENT_FACTORY_CD='KLB' WHERE ROWID='AAC1coAB4AAEuXrAAM';
 
1 row updated.
 
SQL> COMMIT;
 
Commit complete.

 

而後咱們接下來繼續上面實驗, 不過第二個SQL是刪除ROWID='AAC1coAB4AAEuXrAAM'這條記錄,咱們看看實驗結果

 

 

SQL> SELECT USERENV('SID') FROM DUAL;
 
USERENV('SID')
--------------
           925
 
SQL> DELETE FROM INVSUBMAT.INV_TEST WHERE FINAL_GARMENT_FACTORY_CD ='KLB';

 

 

等了大概10秒左右,咱們在會話2執行第二個SQL,發現這個時候,這個SQL2立刻執行完成了。跟以前的實驗現象徹底不一樣

 

clip_image002

 

 

其實出現這樣的現象,是由於第二個會話(SID=917)首先獲取了這一行的TX鎖, 而第一個會話因爲走全表掃描,它還沒掃描到這條記錄。能夠說在一個事務中,對記錄持有X鎖是有順序和時間差的。也就是說會話(SID=917)首先在一行上獲取了TX鎖。

 

 

另外須要注意的是:其實關於Oraclerow lockTX鎖,雖然不少時候咱們把 TX lock叫作row lock , 可是實際上它們是兩回事。row lock是基於數據塊實現的,而TX lock則是經過內存中的ENQUEUE LOCK實現的.它是一種保護共享資源的鎖定機制,一個排隊機制,先進先出(FIFO). 關於這個,這裏不展開敘說。

相關文章
相關標籤/搜索