经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Oracle » 查看文章
Oracle关于TX锁的一个有趣的问题
来源:cnblogs  作者:潇湘隐者  时间:2019/5/22 8:46:24  对本文有异议

前阵子有一个网友在群里问了一个关于Oracle数据库的TX锁问题,问题原文如下:

 

请教一个问题: 两个会话执行不同的delete语句,结果都是删除同一个行。先执行的会话里where条件不加索引走全表扫描,表很大,执行很慢;后执行的用where条件直接用rowid进行delete Oracle的什么机制使第二个会话执行后一直是等待第一个会话结束的呢。

 

那么我们先动手实验一下,来看看这个问题吧,首先,我们需要一个数据量较大的表(数据量大,全表扫描时间长,方便构造实验效果), 这里实验测试的表为INV_TEST,该表在字段FINAL_GARMENT_FACTORY_CD上没有索引。因为我们要构造一个SQL走全表扫描去删除数据。我们更新了两条记录,设置字段FINAL_GARMENT_FACTORY_CD ='KLB'。 如下所示:

 

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

 

首先,在会话1SID=925)里面执行下面SQL语句,删除FINAL_GARMENT_FACTORY_CD ='KLB'的两条记录

 

  1. SQL> SELECT USERENV('SID') FROM DUAL;
  1.  
  1. USERENV('SID')
  1. --------------
  1.            925
  1.  
  1. 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这个条件。

 

 

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

 

 

此时,在会话3,我们使用下面SQL语句查询,就会发现会话2SID=917)被会话1SID=925)阻塞了。

 

 

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

 

 

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

 

 

使用下面脚本,我们知道,会话197ROW_ID=AAC1coABNAAALEKAAA 这条记录上等待获取TX锁,从而导致他被阻塞了。

 

 

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

 

 

clip_image001

 

 

 

其实到这里就可以回答之前网友的问题了。 其实很简单,就是ORACLE数据库的锁机制实现的。我们知道TX锁称为事务锁或行级锁。当Oracle执行DML语句时,系统自动在所要操作的表上申请TM类型的锁。当TM锁获得后,系统再自动申请TX类型的锁,并将实际锁定的数据行的锁标志位进行置位。

 

在数据行上只有X锁(排他锁)。在 Oracle数据库中,当一个事务首次发起一个DML语句时就获得一个TX锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行 DML语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后,TX锁被释放,其他会话才可以加锁。由于第一个SQL语句的执行计划走全表扫描,所以导致这个事务的时间很长,会话2就一直被阻塞,直到第一个会话提交或回滚。

 

另外,我们都知道在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:9533876300346704362

 

那么问题来了,对于会话1SQL走全表扫描,找到FINAL_GARMENT_FACTORY_CD ='KLB'的记录就会在对应的数据行的锁标志进行置位。假如FINAL_GARMENT_FACTORY_CD ='KLB'的记录位于扫描位置的末端呢? 这个实验会是什么样的结果呢?我们用下面SQL找出一些记录。

 

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

 

然后我们将其中一条记录使用下面脚本更新。

 

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

 

然后我们接下来继续上面实验, 不过第二个SQL是删除ROWID='AAC1coAB4AAEuXrAAM'这条记录,我们看看实验结果

 

 

  1. SQL> SELECT USERENV('SID') FROM DUAL;
  1.  
  1. USERENV('SID')
  1. --------------
  1.            925
  1.  
  1. 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). 关于这个,这里不展开叙说。

 

 

 

 

 

原文链接:http://www.cnblogs.com/kerrycode/p/10900096.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号