You are on page 1of 7

Troubleshooting ENQ: TX ROW LOCK CONTENTION

What is a TX lock?

A TX lock is acquired when a transaction initiates its first change and is held until the transaction does a
COMMIT or ROLLBACK. It is used mainly as a queuing mechanism so that other sessions can wait for the
transaction to complete.

The TX lock is interesting as it can occur not only in Mode 6 but Mode 4 as well – see example further
down.

TX lock in mode 6 is an application coding and design problem and can only be alleviated by changing
application code, most likely with more frequent and explicit COMMIT statements and other minor code
changes. The only way to assist is to help identify the objects and queries causing these waits.

These are 2 types of lock modes with TX contention:

Mode 4 – row share lock usually due to Unique Index, Foreign Key or Bitmap Index

Mode 6 – row exclusive lock which a transaction holds until commit or rollback is issued.

Knowing just which lock mode is requested is vital, as the troubleshooting for TX Mode 4 locks will be
different from what is used to troubleshoot Mode 6.

When a TX lock contention occurs, Enq: TX <> wait events are encountered.

A typical AWR report when the database encounters Enq: TX Row Lock contention displays a profile as
below:

This query identifies the Session ID and SQL (could be more than one) of the sessions holding the locks
while the waits are taking place:
select sid, sql_text
from v$session s, v$sql q
where sid in
(select sid from v$session
where state in ('WAITING')
and wait_class != 'Idle'
and event='enq: TX - row lock contention'
and (q.sql_id = s.sql_id or q.sql_id = s.prev_sql_id)
);

The following query provides more details e.g. object causing the locking, sql_id etc

set linesize 200 pagesize 99


col sid form 99999
col object for a20 wrap
col ora_user form a15
col object form a30
col os_pid form 999999
col machine form a20 wrap
col event form a30 wrap
select
l.inst_id
,substr(l.oracle_username,1,10) ora_user
,l.session_id sid
,substr(o.owner||'.'||o.object_name,1,30) object
,p.inst_id
,p.spid os_pid
,DECODE(L.LOCKED_MODE, 0,'NONE',
1,'NULL',
2,'ROW SHARE',
3,'ROW EXCLUSIVE',
4,'SHARE',
5,'SHARE ROW EXCLUSIVE',
6,'EXCLUSIVE',
NULL) LOCK_MODE
,machine
,sql_id
,s.status
,s.event
from sys.gv$locked_object l, dba_objects o, sys.gv$session s, sys.gv$process p
where l.object_id = o.object_id
and l.inst_id = s.inst_id
and l.session_id = s.sid
and s.inst_id = p.inst_id
and s.paddr = p.addr(+)
order by l.inst_id ;
To check the lock mode taken and requested, run the following query - this will only work while the
issue is ongoing as Oracle does not keep any history of lock name and mode.

SELECT sid,type,id1,id2,lmode,request FROM v$lock WHERE type='TX';

e.g.
SID TY ID1 ID2 LMODE REQUEST
----- ---- ---------- ---------- ---------- ----------
588 TX 524299 936708 0 6
587 TX 524299 936708 6 0

Here SID 587 is holding an exclusive row lock (mode 6) and SID 588 is requesting an exclusive row lock,
which results in enq: TX - row lock contention wait.

select sid,event FROM v$session_wait WHERE wait_time= 0 AND event like 'enq%';

SID EVENT
---------- ----------------------------------------------------------------
588 enq: TX - row lock contention

The following query gives information on the segment/s involved – the information in this view is stored
until a database restart so it may also include past history.

col owner form a20


col object_name form a30
col subobject_name form a30
SELECT owner, object_name, subobject_name, value FROM v$segment_statistics
WHERE statistic_name='row lock waits' AND value > 0 ORDER BY 4 DESC;

Lock mode 4

There may be cases where primary key constraints, a unique constraint or a unique index can cause
contention. In mode 4, a TX wait can occur if there is a potential duplicate in a unique index. When two
sessions try to insert the same key value the second session must wait to see if an ORA-001 should be
raised. This can cause the "enq: TX - row lock contention" wait event.

2 sessions attempt an insert with the same primary key – first session not yet committed. These are the
observations:

SID EVENT
---------- ----------------------------------------------------------------
588 enq: TX - row lock contention

SID TY ID1 ID2 LMODE REQUEST


------- -- ---------- ---------- ---------- ----------
588 TX 655375 1024779 6 0
588 TX 262159 985952 0 4
587 TX 262159 985952 6 0

SID 588 is waiting for a TX lock by SID 587 in share mode (4) – SID 588 has a TX lock on its own
transaction (mode 6). X`
A similar situation can occur with bitmap indexes.

For past issues i.e. once data has been flushed from gv$active_session_history,
dba_hist_active_sess_history can be used to drill down on the event to get further details on sessions
waiting by sampling between 2 time windows e.g.,

select * from DBA_HIST_ACTIVE_SESS_HISTORY where SAMPLE_TIME between to_date('27-01-2021


00:00:00','dd-mm-yyyy hh24:mi:ss') and to_date ('27-01-2021 06:00:00','dd-mm-yyyy hh24:mi:ss');
or
SELECT * from DBA_HIST_ACTIVE_SESS_HISTORY where SNAP_ID between &snapid_1 and &snap_id2;

To find out more about the blocking and blocked sessions, this script can be used find out the sql_id’s of
the blocking session and the blocked session (this can be tweaked to get sql_text but would become
unworkable with large SQL queries).

col blocking_status form a150 wrap


SELECT TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS')
||' User ' ||s1.username || '@' || s1.inst_id || ' ( SID=' || s1.sid || ' ) with the sql_id: ' || sqlt2.sql_id ||'
is blocking the SQL statement on ' || s2.username || '@' || s2.inst_id || ' ( SID=' || s2.sid || ' ) blocked
sql_id : ' ||sqlt1.sql_id
AS blocking_status
FROM v$lock l1,
gv$session s1 ,
gv$lock l2 ,
gv$session s2 ,
gv$sql sqlt1 ,
gv$sql sqlt2
WHERE s1.sid =l1.sid
AND s2.sid =l2.sid
AND sqlt1.sql_id= s2.sql_id
AND sqlt2.sql_id= s1.prev_sql_id
AND l1.BLOCK =1
AND l2.request > 0
AND l1.id1 = l2.id1
AND l2.id2 = l2.id2
/

The output is displayed thus:


BLOCKING_STATUS
------------------------------------------------------------------------------------------------------------------------
16-MAR-2023 14:25:58 User SYSTEM@1 ( SID=588 ) with the sql_id: 1d4khm7amjawh
is blocking the SQL statement on SYSTEM@1 ( SID=410 ) blocked sql_id : 1d4khm7amjawh

16-MAR-2023 14:25:58 User SYSTEM@1 ( SID=588 ) with the sql_id: 1d4khm7amjawh


is blocking the SQL statement on SYSTEM@1 ( SID=587 ) blocked sql_id : dj0sb1xxcnqd6

Deeper analysis of Mode 6 locks

Looking at the p1 value from this query

select
s.username username,
s.sid,
e.event event,
e.p1text,
e.p1,
e.state
from v$session s, v$session_wait e
where s.username is not null
and s.sid = e.sid
and e.event like '%enq:%'
order by s.username, upper(e.event);

USERNAME SID EVENT P1TEXT P1 STATE


------------------------------ ---------- ------------------------------ ------------ ---------- -------------------
SYSTEM 404 enq: TX - row lock contention name|mode 1415053318 WAITING

The P1 value of this type of lock is always the same – 1415053318. For mode 4 locks, this value is
1415053316.

The following query when the row locks are in progress will also show the lock mode

SELECT chr(to_char(bitand(p1,-16777216))/16777215)|| chr(to_char(bitand(p1, 16711680))/65535)


"Lock",
to_char( bitand(p1, 65535) ) "Mode"
FROM v$session_wait
WHERE event like '%enq:%';

Lock Mode
-------- ---------
TX 6

The value TX is derived from the hex value of 1415053318


There is also a way to use Logminer to analyze this contention

SELECT sid, blocking_session, event FROM v$session LEFT OUTER JOIN v$sqlarea USING (sql_id) WHERE
nvl(blocking_session,sid) IN (SELECT holding_session FROM dba_blockers);

SID BLOCKING_SESSION EVENT


---------- ------------- ----------------------------
404 211 enq: TX - row lock contention
211 SQL*Net message from client

SELECT t.xidusn, t.xidslot, t.xidsqn, t.start_time, t.start_scn


FROM v$transaction t JOIN v$session s ON t.addr = s.taddr
WHERE s.sid = 211;

XIDUSN XIDSLOT XIDSQN START_TIME START_SCN


---------- ---------- ---------- -------------------- ---------------
2 9 881866 03/17/23 13:02:06 12205614783505

SELECT name
FROM v$archived_log
WHERE 12205614783505 BETWEEN first_change# AND next_change# - 1;

NAME
------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------
/u02/data2/fra/EM12/archivelog/1_30089_900792223.dbf

Start Logminer

EXECUTE
dbms_logmnr.add_logfile(logfilename=>'/u02/data2/fra/EM12/archivelog/1_30089_900792223.dbf');
EXECUTE dbms_logmnr.start_logmnr(options=>dbms_logmnr.dict_from_online_catalog);

SELECT sql_redo
FROM v$logmnr_contents
WHERE xidusn = 2
AND xidslt = 9
AND xidsqn = 881866;

SQL_REDO
------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------
set transaction read write;
select * from "SYSTEM"."ANIL" where ROWID = 'AATUKqAABAAAn2ZAAA' for update;
select * from "SYSTEM"."ANIL" where ROWID = 'AATUKqAABAAAn2ZAAB' for update;
select * from "SYSTEM"."ANIL" where ROWID = 'AATUKqAABAAAn2ZAAE' for update;

End logminer

EXECUTE dbms_logmnr.end_logmnr;

You might also like