Identify Locked Objects in Oracle Database
Identify Locked Objects in Oracle Database using v$locked_object, dba_objects, v$lock and v$session
Purpose
A developer reports that an INSERT that should finish in seconds is still running after ten minutes. No error has come back to the application, no timeout has fired, and nothing is moving. In the vast majority of such cases the root cause is a database lock: a session with an uncommitted DML transaction is holding a row or table lock, and the waiting session cannot proceed until that lock is released. The four-view join below makes the diagnosis immediate.
V$LOCKED_OBJECT contains a row for every object held under an active lock and identifies the session responsible. DBA_OBJECTS resolves the numeric object IDs stored in that view into human-readable schema and object names. V$LOCK adds the lock mode — expressed as an integer that the query decodes into labels such as Row-X (SX) or Exclusive — and a BLOCK flag that indicates whether the holding session is actively preventing other sessions from progressing. V$SESSION supplies the Oracle and operating-system usernames for each session, plus the SID,SERIAL# pair that is the argument to ALTER SYSTEM KILL SESSION if the application cannot recover on its own.
The join of all four views takes fractions of a second and reads only dynamic performance structures. It is safe to run repeatedly on a busy production database. The result set names what is locked, who holds it, and whether the situation is actively blocking other work — all in a single pass.
Code
1set lines 100 pages 999
2col username format a20
3col sess_id format a10
4col object format a25
5col mode_held format a10
6
7select oracle_username || ' (' || s.osuser || ')' username
8, s.sid || ',' || s.serial# sess_id
9, owner || '.' || object_name object
10, object_type
11, decode( l.block
12 , 0, 'Not Blocking'
13 , 1, 'Blocking'
14 , 2, 'Global') status
15, decode(v.locked_mode
16 , 0, 'None'
17 , 1, 'Null'
18 , 2, 'Row-S (SS)'
19 , 3, 'Row-X (SX)'
20 , 4, 'Share'
21 , 5, 'S/Row-X (SSX)'
22 , 6, 'Exclusive', TO_CHAR(lmode)) mode_held
23from v$locked_object v
24, dba_objects d
25, v$lock l
26, v$session s
27where v.object_id = d.object_id
28and v.object_id = l.id1
29and v.session_id = s.sid
30order by oracle_username
31, session_id
32/
Code Breakdown
Display setup
SET LINES 100 PAGES 999 adjusts SQL*Plus output formatting. A line width of 100 characters prevents the columns from wrapping mid-value; setting pages to 999 stops the column headers from repeating after every 24 rows in a long result set. The four COL FORMAT commands then allocate fixed character widths to each output column.
col username format a20— space for the combined Oracle and OS username. Shops with long schema names or service-account names should increase this toa30.col sess_id format a10— sufficient for standardSID,SERIAL#values. Very large serial numbers may requirea12.col object format a25— forowner.object_name. Objects with longer names may truncate; adjust toa35if needed.col mode_held format a10— accommodates the longest decode result,S/Row-X (SSX). Increase toa14if values appear cut off.
The SELECT clause in detail
oracle_username || ' (' || s.osuser || ')' username
ORACLE_USERNAME is the database login account — the schema name. OSUSER from V$SESSION is the operating-system account that opened the connection. For JDBC connection pools the OS user is typically a service account; for interactive SQL*Plus or IDE sessions it is the developer's or DBA's workstation login. Combining them in one column makes it immediately clear whether the locking session is an application process or a person sitting at a terminal who forgot to commit.
s.sid || ',' || s.serial# sess_id
SID is the session identifier. It uniquely marks an active session but is recycled when a session disconnects and a new connection takes the same slot. SERIAL# increments each time a SID is reused. The pair SID,SERIAL# is unambiguous — it names one specific session in history — and it is the exact argument to ALTER SYSTEM KILL SESSION 'sid,serial#'. Never use SID alone; you risk targeting a different session that has taken the slot since the original disconnected.
owner || '.' || object_name object
V$LOCKED_OBJECT stores only a numeric OBJECT_ID. The join to DBA_OBJECTS resolves that number into the schema-qualified name. The fully qualified form — HR.EMPLOYEES rather than just EMPLOYEES — is essential in multi-schema databases where the same table name exists under more than one owner.
object_type
The kind of database object that is locked: TABLE, INDEX, SEQUENCE, PROCEDURE, and so on. Most DML contention in OLTP systems occurs on TABLE objects. An INDEX appearing here often indicates a function-based index or a domain index is involved. A SEQUENCE lock at mode 3 is normal and extremely brief.
The DECODE on l.block — blocking status
V$LOCK.BLOCK uses three values to describe how a lock interacts with other sessions:
| Value | Label | Meaning |
|---|---|---|
| 0 | Not Blocking | Lock is held but no other session is currently waiting for it |
| 1 | Blocking | At least one other session is waiting for a lock this session holds |
| 2 | Global | Lock involves another RAC node; cross-instance blocking in a Real Application Clusters environment |
During an incident, focus first on rows with Blocking status. Rows with Not Blocking are held but harmless in the immediate term. Global rows require checking the other RAC node using GV$LOCKED_OBJECT and GV$LOCK.
The DECODE on v.locked_mode — lock strength
Oracle assigns integer codes to the six lock modes that DML and DDL operations can acquire. The DECODE translates these codes into readable labels:
| Code | Label | Description |
|---|---|---|
| 0 | None | No lock held in this record |
| 1 | Null | Claimed but not restrictive; used in distributed environments |
| 2 | Row-S (SS) | Sub-share; prevents full-table exclusive locks only |
| 3 | Row-X (SX) | Sub-exclusive; the mode acquired by standard INSERT, UPDATE, DELETE |
| 4 | Share | Prevents DML from other sessions on the whole table |
| 5 | S/Row-X (SSX) | Share sub-exclusive; prevents most DML and some DDL |
| 6 | Exclusive | Full exclusive; blocks all DML and DDL from any other session |
In a healthy OLTP environment, almost all locks are mode 3 (Row-X), held for milliseconds during a commit cycle. Mode 4 (Share) or mode 6 (Exclusive) points to an explicit LOCK TABLE statement, a DDL operation, or a bulk-load utility. A mode 6 lock held by an idle session with a large LAST_CALL_ET almost always means an uncommitted interactive transaction left open by a developer.
FROM and WHERE clauses
The query uses comma-style implicit join syntax across the four views. The three join conditions are:
v.object_id = d.object_id— joinsV$LOCKED_OBJECTtoDBA_OBJECTSto convert the stored numeric object ID into a readable name and type.v.object_id = l.id1— joins toV$LOCK. For table-level lock entries,ID1inV$LOCKholds the object ID. This join retrieves theBLOCKflag and the numericLMODEvalue.v.session_id = s.sid— joins toV$SESSIONto pull the Oracle username, OS username, and theSID,SERIAL#pair for each session holding a lock.
ORDER BY clause
Ordering first by oracle_username then by session_id groups all locks held by the same user or application account together. When a single service account holds locks across five tables, those five rows appear consecutively, making it easy to see the full scope of one open transaction without scanning the whole result set.
Tracing the Full Blocking Chain
The four-view join identifies every session holding a lock, but in a busy incident you often need to see the cascade tree: which session is the ultimate root of a chain, and how many sessions branch from it. V$SESSION.BLOCKING_SESSION stores the SID of the session that directly blocks each waiting session. Two supplementary queries use that column to build the full picture without re-joining the locked-object views.
1-- Step 1: All sessions currently waiting on another session's lock
2SELECT s.sid AS blocked_sid
3, s.serial#
4, s.username AS blocked_user
5, s.blocking_session AS blocker_sid
6, s.status
7, s.last_call_et AS wait_seconds
8, s.event AS wait_event
9FROM v$session s
10WHERE s.blocking_session IS NOT NULL
11ORDER BY s.blocking_session
12, s.sid;
13
14-- Step 2: The root holder — blocks others but is not itself blocked
15SELECT s.sid
16, s.serial#
17, s.username
18, s.osuser
19, s.status
20, s.last_call_et AS idle_seconds
21, t.start_time AS txn_open_since
22FROM v$session s
23 JOIN v$transaction t ON t.addr = s.taddr
24WHERE s.sid IN (
25 SELECT blocking_session
26 FROM v$session
27 WHERE blocking_session IS NOT NULL
28 )
29AND s.blocking_session IS NULL
30ORDER BY s.last_call_et DESC;
Reading step 1
The first query returns one row per waiting session. BLOCKER_SID points to the session that must release its lock for the waiter to continue. When several rows share the same BLOCKER_SID, a single uncommitted transaction is cascading across multiple waiting sessions. WAIT_EVENT identifies what the waiting session is blocked on — for row-level DML conflicts the event is typically enq: TX - row lock contention; for table-level DDL conflicts it is enq: TM - contention.
Reading step 2
The second query isolates the root cause: the session holding a lock that blocks others, but not itself waiting for any other session. Two fields here drive the decision on whether to escalate:
IDLE_SECONDS(LAST_CALL_ET) — how many seconds since the session last made a database call. A large value combined withSTATUS = INACTIVEis the signature of an open, uncommitted transaction belonging to a session that has stopped responding. This is the most common cause of multi-session blocking cascades in OLTP systems.TXN_OPEN_SINCE— the wall-clock timestamp when the holding transaction started, fromV$TRANSACTION.START_TIME. This establishes the age of the transaction independent of session activity. A transaction open for three hours during an overnight batch is usually an application defect, not a brief lock acquisition.
Together, the two queries answer the three questions a DBA needs before escalating: how many sessions are waiting, who is the ultimate root holder, and how long that session has been idle or its transaction has been open.
Finding downstream sessions blocked by an intermediate blocker
A chain can have multiple levels: A blocks B, B blocks C, C blocks D. Session B appears in both queries — it is blocked by A (step 1) and is also a blocker for C (step 2 would miss it because blocking_session IS NOT NULL). To expose the full chain, modify step 2 to remove the AND s.blocking_session IS NULL filter and instead include the depth level:
1-- Full chain with immediate blocker shown for each session
2SELECT s.sid
3, s.blocking_session AS blocked_by
4, s.username
5, s.status
6, s.last_call_et AS idle_seconds
7, s.event
8FROM v$session s
9WHERE s.sid IN (
10 SELECT blocking_session FROM v$session WHERE blocking_session IS NOT NULL
11 UNION
12 SELECT sid FROM v$session WHERE blocking_session IS NOT NULL
13 )
14ORDER BY s.blocking_session NULLS FIRST, s.sid;
This version includes both holders and waiters in one result set. Sessions with a NULL BLOCKED_BY are root holders; sessions with a value in that column are somewhere in the wait chain. Walking the tree from root to leaf shows the complete cascade scope.
Key Points
V$LOCKED_OBJECTcaptures all active DML locks. Every object locked by an open transaction appears here regardless of mode. The row persists until the transaction commits, rolls back, or the session is terminated.BLOCK = 1is the incident indicator. A row withNot Blockingis held but currently harmless. TheBlockingrow is the root cause to address first.SID,SERIAL#is the only safe kill handle. UsingSIDalone risks targeting a different session that has taken the slot since the original disconnected. Always capture both values before issuingALTER SYSTEM KILL SESSION.- Locks clear automatically at commit or rollback. No manual cleanup is needed once the holding transaction ends. Re-running the query after the transaction closes will show those rows gone.
- In RAC, use the GV$ equivalents.
GV$LOCKED_OBJECT,GV$LOCK, andGV$SESSIONadd anINST_IDcolumn and cover all cluster nodes. A blocker on node 2 is invisible to the node-1V$views. - In a CDB (Oracle 12c and later), container context matters. Connected to a PDB,
V$LOCKED_OBJECTshows only that PDB's locks. Connected to the root (CDB$ROOT) with theCONTAINER = ALLclause, the view includes locks across all PDBs and exposes aCON_IDcolumn that identifies which pluggable database each lock belongs to. For cross-container lock investigations on 12c and later, useCDB_OBJECTSin place ofDBA_OBJECTSto resolve object names across containers. BLOCKING_SESSIONis a direct pointer. Available since Oracle 10g Release 1,V$SESSION.BLOCKING_SESSIONholds the SID of the session blocking a waiting session, andBLOCKING_SESSION_STATUS(introduced in 11g) explains why it may be NULL (NO HOLDER,UNKNOWN,NOT IN WAIT, orVALID). These two columns are the fastest route to the root cause when the full four-view join is not convenient.- Privilege requirement. The query accesses
DBA_OBJECTSand the fixedV$views. The executing account needsSELECT_CATALOG_ROLEorSELECT ANY DICTIONARY. Running as a DBA account avoids privilege errors.
Insights and Best Practices
Follow the blocking chain, not just the first row
One blocking session can cascade into a queue. Session A holds a lock; Session B is waiting for it and is therefore blocking Session C; Session C is blocking Session D. The BLOCK = 1 row identifies only the root holder — the one whose commit or rollback would free everyone downstream. To trace the full chain, query V$SESSION.BLOCKING_SESSION starting from the waiting sessions and follow the links upward, using the supplementary queries shown above. Knowing the chain length tells you how many users or jobs are affected and how urgently to respond.
Check the session before deciding to kill
Before issuing ALTER SYSTEM KILL SESSION, confirm the session is genuinely stuck rather than mid-transaction. In V$SESSION, a STATUS of INACTIVE combined with a large LAST_CALL_ET (seconds since the last database call) is the classic profile of a session that has not communicated with the database for an extended period — a developer who ran an UPDATE, confirmed the results, and walked away without committing. A session with ACTIVE status may be mid-processing and should be treated differently.
Idle open transactions are the most common source of blocking
In OLTP environments, the most frequent locking incident is not application code with a defect — it is an interactive session that executed DML and never committed. SQL*Plus, SQL Developer, and many database IDE tools default to manual-commit mode. A developer who runs UPDATE emp SET salary = salary * 1.1 WHERE empno = 7934, reviews the result, and then takes a break has locked that row for the duration. A monitoring query that alerts when any BLOCK = 1 session has LAST_CALL_ET > 300 catches this pattern proactively without requiring a DBA to watch continuously.
Distinguish row-level from table-level locks by mode
Standard INSERT, UPDATE, and DELETE acquire mode 3 (Row-X). This holds only the specific rows modified, not the entire table — other sessions can modify different rows simultaneously. Mode 4 (Share) or mode 6 (Exclusive) signals a full-table lock from an explicit LOCK TABLE statement, a DDL operation, or a legacy import utility. A table-level lock from an unexpected session is more urgent because it blocks all DML against that object, not just conflicting row-level operations.
Use this query as step one in a locking runbook
A structured response to lock contention saves time and avoids missteps. Step one: run this query to identify the blocking session's SID,SERIAL#, the object locked, and the lock mode. Step two: query V$SESSION.SQL_ID for that SID to find the last SQL the session executed. Step three: look up the SQL text in V$SQL using that SQL_ID. Those three steps — who is blocking, what session, what statement — produce the information the application team needs to investigate and give the DBA the evidence needed to justify escalation to a forced kill if the application cannot recover.
Create a scheduled lock-monitoring check
For databases where lock contention is a recurring operational concern, wrap this query in a DBMS_SCHEDULER job or a monitoring-tool alert rule that fires when BLOCK = 1 rows appear alongside LAST_CALL_ET exceeding a threshold — for example, 300 seconds. Proactive alerting removes the lag between "lock appears" and "DBA is notified," which in overnight batch windows can be the difference between resolving the issue in minutes and discovering it when the batch has missed its 06:00 completion deadline.
When to Use This Check
- When an application or user reports that a transaction is hanging with no error returned.
- Before a maintenance window that includes DDL, to confirm no active DML transactions are in flight on the target objects.
- During high-concurrency periods such as month-end processing or batch settlement windows, as part of a routine operational check.
- When AWR or Active Session History shows a rise in
enq: TX - row lock contentionorenq: TM - contentionwait events. - After a new application deployment, to verify the new code does not hold transactions open longer than the previous version.
- When a bulk data load or import appears to stall without returning an error to the calling process.
- During on-call troubleshooting when the cause of a slowdown is unknown and lock contention is one of the candidates to rule in or out.
- When the
V$SESSION.BLOCKING_SESSIONchain query reveals a multi-level cascade and you need the object names and lock modes to construct a full incident report.
Troubleshooting Common Issues
If the query returns no rows, no DML locks are currently held. The contention may have resolved itself between the report and the query, or the issue is not a lock at all — CPU saturation, full undo tablespace, or a network hang at the application tier can all produce application-side hangs without any lock visible in these views. Re-run the query at the next reported incident rather than concluding that locking is not a factor.
If the query returns many Not Blocking rows for the same session with mode Row-X, that session is holding locks across many rows or objects but is not stopping anyone else. This is normal for a large batch transaction that has not yet committed. It becomes a concern only if the session is inactive and the transaction is old. Check V$TRANSACTION.START_TIME to measure how long the transaction has been open.
If the step-2 blocking-chain query (V$SESSION.BLOCKING_SESSION) returns NULL for sessions you expect to be blockers, check V$SESSION.BLOCKING_SESSION_STATUS. A value of UNKNOWN can appear in RAC environments when the blocking session is on a different node — switch to GV$SESSION and filter by INST_ID to find the holder across all instances. A value of NO HOLDER means the session is waiting on a lock that has no current holder — this can occur briefly during a deadlock resolution sequence.
If you see Block = Global rows in a single-instance environment where RAC is not expected, verify that the CLUSTER_DATABASE initialization parameter is set to FALSE. This status can appear as an artifact after migrating from a RAC cluster to a standalone instance without resetting the parameter.
If the query fails with ORA-00942: table or view does not exist, the executing account lacks the privilege to see DBA_OBJECTS or the V$ fixed-view synonyms. Grant SELECT_CATALOG_ROLE, or run the query as a user with DBA privileges.
References
- V$LOCKED_OBJECT — Oracle Database Reference 19c — Authoritative column definitions for the locked-object dynamic performance view, including ORACLE_USERNAME, SESSION_ID, OBJECT_ID, and LOCKED_MODE
- V$LOCK — Oracle Database Reference 19c — Column reference for V$LOCK, covering the BLOCK flag that identifies blocking sessions and the LMODE integer codes for lock strength
- V$SESSION — Oracle Database Reference 19c — The authoritative source for SID, SERIAL#, USERNAME, OSUSER, STATUS, BLOCKING_SESSION, BLOCKING_SESSION_STATUS, and LAST_CALL_ET columns used in locking investigations
- Data Concurrency and Consistency — Oracle Database Concepts 19c — Oracle's conceptual guide to the locking model, lock modes, row-level versus table-level locking, and how transactions interact with one another
- Killing Oracle Sessions — oracle-base.com — Tim Hall's practical guide to identifying the correct SID and SERIAL# for a blocking session and issuing ALTER SYSTEM KILL SESSION safely, including immediate and post-wait kill modes