> 文档中心 > Oracle-enq: TX - row lock contention 等待事件分析

Oracle-enq: TX - row lock contention 等待事件分析

什么是enq:TX - row lock contention等待:

等待事件enq:TX - row lock contention 是Oracle常见的几大等待事件之一,在开启的事务中,为了维护事务数据的一致性,会在事务所涉及的修改行中添加TX锁以防止其他会话同时修改数据,当其他会话等待该TX锁的释放时,就会产生enq:TX -row lock contention等待事件。

事件p1,p2,p3含义:

• P1 = name|mode

• P2 = usn<<16 | slot

• P3 = sequence

• name|mode

The lock name and requested mode are encoded into P1 as "name<<16 | mode". This is best seen in P1RAW (or you can convert P1 to hexadecimal).

For this wait:

§ name is always the ASCII for "TX" = 0x5458.

§ mode is the mode that the TX lock is being requested in.

In most cases mode will be 6 . Occassionally it may be 4:

□ "6" = eXclusive mode TX wait.

□ "4" = Shared mode TX wait.

eg: P1RAW of 54580006 = eXclusive mode TX wait, P1RAW of 54580004 = Shared mode TX wait.

• usn<<16 | slot

P2RAW indicates the ID1 part of the TX lock being waited on, which is an encoding of the undo segment (usn) and the undo slot.

• sequence

P3RAW indicates the ID2 part of the TX lock being waited on, which is the undo slot sequence number.

常见的TX锁等待原因:

1 应用代码逻辑层有问题,导致同时修改相同数据引发锁等待。

2 应用代码逻辑层有问题,导致事务不提交引发锁等待。

3 主键或者唯一键冲突引发锁等待。

4 位图索引维护引发锁等待。

5 事务回滚导致的锁等待。

6 慢SQL导致的锁等待。

如何分析收集锁等待信息

1 通过awr,ash或者addm去获取。

@?/rdbms/admin/awrrpt@?/rdbms/admin/ashrpt@?/rdbms/admin/addmrpt

AWR报告解读:

从Top Event等待查看enq:TX的DBTIME 占比以及平均等待wait avg

从Row Lock Waits去确认发生锁等待的对象

ASH报告解读:

从Top SQL with Top Events确认产生锁等待的SQL_TEXT。

从Top Blocking Sessions确认主要的堵塞会话信息。

addm报告解读:

可以从里面发现等待的对象,堵塞源以及等待的SQL。

2 查询enq:TX - row lock contention等待事件信息。

col sid for 9999col serial# for 9999col program for a30col event for a30col state for a10col sql_text for a50col inst_id for 9select a.inst_id,a.sid,a.serial#,a.program,a.state,b.event,b.p1,b.p1raw,b.p2,b.p2raw,b.p3,b.p3raw,b.SECONDS_IN_WAIT,a.BLOCKING_INSTANCE,a.BLOCKING_SESSION,a.BLOCKING_SESSION_STATUS,a.sql_id,c.sql_textfrom gv$session a,gv$session_wait b,gv$sql cwhere a.sid=b.sid and a.inst_id=b.inst_id and a.event=b.event and b.event like '%TX%' and a.sql_id=c.sql_id  and a.inst_id=c.inst_id;

主要获取的TX等待事件涉及的当前会话信息,等待事件信息,堵塞会话信息以及执行的SQL。

#从当前的查询信息,我们可以确认会话(sid=54,47) 被会话(blocking_session=44)堵塞,当前的请求锁信息为TX-6级锁(P1raw=0000000054580006),执行的sql_text INST_ID   SID SERIAL# PROGRAM      STATE     EVENT   P1 P1RAW   P2 P2RAW P3 P3RAW SECONDS_IN_WAIT BLOCKING_INSTANCE BLOCKING_SESSION BLOCKING_SE SQL_ID     SQL_TEXT---------- ----- ------- ------------------------------ ---------- ------------------------------ ---------- ---------------- ---------- ---------------- ---------- ---------------- --------------- ----------------- ---------------- ----------- ------------- --------------------------------------------------   1    54      19 sqlplus@rac1 (TNS V1-V3)  WAITING    enq: TX - row lock contention  1415053318 0000000054580006    655362 00000000000A0002   777 0000000000000309     1758   2  44 VALIDauzjshzj54dut update test.test_1 set name1='1bbbbbbc' where id=1   2    47      17 sqlplus@rac2 (TNS V1-V3)  WAITING    enq: TX - row lock contention  1415053318 0000000054580006    655362 00000000000A0002   777 0000000000000309     1770   2  44 VALIDauzjshzj54dut update test.test_1 set name1='1bbbbbbc' where id=1#将语句查询的p1,p2值带入v$lock,可以查询请求的锁信息set linesize 400select *from v$lockwhere id1=524288 and id2=1318ADDR      KADDRSID TY ID1    ID2     LMODE    REQUEST   CTIME      BLOCK---------------- ---------------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------0000000081D912B0 0000000081D91308    50 TX     524288   1318  0      6 169540

如果查询的会话信息已经断开,这可以从内存历史视图去获取。

col SESSION_ID for 9999col SESSION_serial# for 9999col program for a30col event for a30col session_state for a10col sql_text for a50col inst_id for 9select a.SAMPLE_TIME,a.inst_id,a.SESSION_ID,a.SESSION_serial#,a.program,a.session_state,b.event,b.p1,b.p2,b.p3,b.wait_time,a.BLOCKING_INST_ID,a.BLOCKING_SESSION,a.BLOCKING_SESSION_STATUS,a.sql_id,c.sql_textfrom gv$active_session_history a,gv$session_wait_history b,gv$sql cwhere a.SESSION_ID=b.sid and a.inst_id=b.inst_id and a.event=b.event and b.event like '%TX%' and a.sql_id=c.sql_id  and a.inst_id=c.inst_idand a.sample_time between timestamp'2022-05-17 00:00:00' and timestamp'2022-05-18 00:00:00';

如果想追溯时间更久远的,可以查询历史视图去获取。

#查看等待事件的信息set linesize 400set pagesize 400col sample_time for 40col sql_text for a60col event for a40select to_char(a.SAMPLE_TIME,'yyyymmdd hh24:mi:ss'),a.SESSION_ID,a.SESSION_SERIAL#,a.event,a.p1,a.p2,a.p3,a.sql_id,a.BLOCKING_SESSION,a.BLOCKING_SESSION_SERIAL#,b.sql_textfrom DBA_HIST_ACTIVE_SESS_HISTORY a,dba_hist_sqltext bwhere a.SAMPLE_TIME between to_date('2022-05-16 00:00:00','yyyy-mm-dd hh24:mi:ss') and to_date('2022-05-17 00:00:00','yyyy-mm-dd hh24:mi:ss')  and a.sql_id=b.sql_id      AND D.EVENT = 'enq: TX - row lock contention'order by to_char(a.SAMPLE_TIME,'yyyymmdd hh24:mi:ss') ;#查看争用对象SELECT  D.SESSION_ID,      D.SESSION_SERIAL#,      D.current_obj#,      D.current_file#,      D.current_block#,      D.current_row#,D.EVENT,      D.P1TEXT,      D.P1,      D.P2TEXT,      D.P2,      CHR(BITAND(P1, -16777216) / 16777215) ||      CHR(BITAND(P1, 16711680) / 65535) "Lock",      BITAND(P1, 65535) "Mode",      D.BLOCKING_SESSION,      D.BLOCKING_SESSION_STATUS,      D.BLOCKING_SESSION_SERIAL#,      D.SQL_ID,      TO_CHAR(D.SAMPLE_TIME, 'YYYYMMDDHH24MISS') SAMPLE_TIME FROM DBA_HIST_ACTIVE_SESS_HISTORY DWHERE D.SAMPLE_TIME BETWEEN TO_DATE('2022-05-16 00:00:00', 'yyyy-mm-dd hh24:mi:ss') AND     TO_DATE('2022-05-17 00:00:00', 'yyyy-mm-dd hh24:mi:ss')  AND D.EVENT = 'enq: TX - row lock contention'order by D.BLOCKING_SESSION;  SELECT * FROM dba_objects D WHERE D.object_id=87620;

3 查询锁信息

set linesize 400set pagesize 400col object_name for a40select a.inst_id,a.sid,a.type,a.id1,a.id2,b.owner,b.object_name,a.lmode,a.requestfrom gv$lock a,dba_objects bwhere a.id1=b.object_id(+) and a.type in ('TM','TX')order by a.inst_id,a.sid;

可以查看当前哪些会话持有了哪些对象的TX,TM锁,哪些会话请求对象的TX,TM锁。

#SID-44 持有了TEST.TEST_1的TM-3以及TX-6锁,SID-47,SID-54持有TEST.TEST_1的TM-3锁,请求TX-6锁  INST_ID    SID TYID1     ID2  OWNER     OBJECT_NAME    LMODE    REQUEST---------- ---------- -- ---------- ---------- ----------------------- ---------------------------------------- ---------- ----------   1   54 TM      880600 TEST     TEST_1     3     0   1   54 TX     655362     7770     6   2   44 TX     655362     7776     0   2   44 TM      880600 TEST     TEST_1     3     0   2   47 TX     655362     7770     6   2   47 TM      880600 TEST     TEST_1     3     0

4 查询堵塞链

单实例查询堵塞链源头。

---sql1set lines 200 pages 100col tree for a30col event for a40select *  from (select a.sid, a.serial#, a.sql_id, a.event, a.status, connect_by_isleaf as isleaf, sys_connect_by_path(SID, ' 0 AND l1.id1 = l2.id1 AND l2.id2 = l2.id2;#54正堵着53------------------------------------------------------SYS@nrac1 ( SID=54 )  is blocking SYS@nrac1 ( SID=53 )---sql3set linesize 200col user_name for a20col owner for a20col object_name for a30 SELECT /*+ rule */ LPAD (' ', DECODE (l.xidusn, 0, 3, 0))||l.oracle_username User_name,o.owner,o.object_name,o.object_type,s.sid,s.serial#      FROM   v$locked_object l, dba_objects o, v$session s     WHERE   l.object_id = o.object_id AND l.session_id = s.sid  ORDER BY   o.object_id, xidusn DESC   USER_NAMEOWNER      OBJECT_NAMEOBJECT_TYPE SID    SERIAL#-------------------- -------------------- ------------------------------ ------------------- ---------- ----------SYS   --堵塞   SYS      BLOCKING1TABLE    54   37   SYS--被堵塞 SYS      BLOCKING1TABLE    53   27SYS      SYS      BLOCKING2TABLE    57   55   SYS  SYS      BLOCKING2TABLE    59   17   SYS  SYS      BLOCKING2TABLE    58   53

集群查询堵塞链源头。

select *  from (select a.inst_id, a.sid, a.serial#, a.sql_id, a.event, a.status, connect_by_isleaf as isleaf, sys_connect_by_path(a.SID||'@'||a.inst_id, ' <- ') tree, level as tree_level   from gv$session a  start with a.blocking_session is not null connect by (a.sid||'@'||a.inst_id) = prior (a.blocking_session||'@'||a.blocking_instance)) where isleaf = 1 order by tree_level asc;

<- 50@1 <- 55@2:表示实例一SID:50被实例二SID:55所堵塞,堵塞源头为实例二SID:55。

INST_ID    SID SERIAL# SQL_ID EVENT   STATUS      ISLEAF   TREE TREE_LEVEL---------- ---- ------- ------------- ------------------------------- -------------- -------- --------------    -----------------   2  55      17    SQL*Net message from client INACTIVE    1<- 50@1 <- 55@2    2   2   17    SQL*Net message from client INACTIVE    1<- 29@2 <- 55@2     2

5 查询等待的对象,行数据

#根据堵塞会话,查询被堵塞的object_id以及对应的file,blockSQL> select sid,row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#  from v$session where blocking_session=55 and blocking_instance=2; SID ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW#---- ------------- -------------- --------------- -------------  50880604   2868 0object_id:88060file#:4block#:2868#根据object_id:88060查询等待发生的对象col owner for a20col object_name for a40select owner,object_name from dba_objects where object_id=88060;SQL> SQL> OWNER  OBJECT_NAME-------------------- ----------------------------------------TEST  TEST_1#根据file=4查询等待发生对象所在的文件SELECT rfile#, file# , name FROM   v$datafile WHERE  file# = 4;RFILE#  FILE#---------- ----------NAME----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------   4      4+DATA/dbocs/datafile/users.269.1036073665#根据blocking_session 55查询等待发生行的rowidcol  object_name for a30SELECT do.object_name ,s.row_wait_obj#  ,s.row_wait_file# ,s.row_wait_block#,s.row_wait_row#  ,dbms_rowid.rowid_create( 1, data_object_id, rfile#, ROW_WAIT_BLOCK#, ROW_WAIT_ROW# )FROM   v$session s,dba_objects do,v$datafile vWHERE  s.blocking_session = 55     AND    s.row_wait_obj# = do.object_idAND    s.row_wait_file# = v.file#;OBJECT_NAME   ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# DBMS_ROWID.ROWID_C------------- ------------- -------------- --------------- ------------- ------------------TEST_1      88060      4 28680 AAAVf8AAEAAAAs0AAA#根据rowid查询等待的行SQL> select * from test.test_1 where rowid='AAAVf8AAEAAAAs0AAA';  ID   NAME1     NAME2    NAME3 ST---------- ------------------------------ ------------------------------ ------------------------------ --   1  1aaaaaa 1bbbbbbbbb1ccccccccc      0

获取引发TX锁等待的SQL的执行计划,索引信息

#查看SQL执行计划set linesize 400set pagesize 400select * from table(dbms_xplan.display_cursor('sql_id',null,'advanced -PROJECTION allstats last'));#查看索引信息select a.index_owner,a.table_name,a.column_name,a.index_name,b.index_typefrom dba_ind_columns a,dba_indexes bwhere a.index_owner=b.owner and a.index_name=b.index_name and a.table_name='';

7 查询堵塞源会话未提交语句

有时候查询到的堵塞源会话当前的执行sql是select查询或者空闲无执行sql状态,这通常发生在一个事务包含多条sql或者空闲事务不提交,在这种情形下,我们需要去查询堵塞源会话的打开游标信息以及堵塞源的事务信息,从里面获取当前未提交的执行SQL以及事务信息。

---查询游标信息#需要注意的是查询的结果不一定就是当前会话已经执行过的全部sql,因为有些游标不一定有缓存,可能执行完就直接关闭结束了。set linesize 200set long 99999set pagesize 2000select o.user_name,o.sid,o.sql_id,s.sql_fulltext,o.cursor_type,o.LAST_SQL_ACTIVE_TIMEfrom v$open_cursor o,v$sql swhere o.sql_id=s.sql_id and o.sid=476;---查询事务信息select b.* from gv$session_wait a,gv$transaction bwhere  a.event='enq: TX - row lock contention' and trunc(a.p2/power(2,16)) = b.xidusnand (bitand(a.p2,to_number('ffff','xxxx'))+0) = b.xidslot and a.p3 = b.xidsqn;

如果查询不到SQL,这应该从应用逻辑代码入手,分析sql的执行情况。

总结:

产生TX锁等待的原因一般都是应用程序的逻辑问题,表,索引设计问题以及性能问题导致,在分析时,要收集等待的对象,操作的SQL,堵塞的上下游,等待对象TX锁的持有级别,请求级别以及SQL的性能来确认TX锁等待的原因。