Global Enqueue Service (GES), Blocking, Locks & Deadlocks
In Part 16, we used AWR and ASH to identify where RAC performance problems come from.
Now we go one level deeper.
This part is about one of the most important RAC troubleshooting skills:
Finding why one session is blocking another session — especially when the sessions are running on different RAC instances.
We’ll move from theory to production-style troubleshooting.
1. GCS vs GES — Don’t Mix Them Up
Oracle RAC has two major global coordination mechanisms.
Oracle RAC
|
+---------+---------+
| |
GCS GES
| |
Global Cache Service Global Enqueue Service
| |
Data blocks Locks / Enqueues
| |
Cache Fusion Global locking
GCS
GCS manages access to cached database blocks.
Typical waits:
gc cr request gc current request gc current block busy gc buffer busy
GES
GES manages global enqueues and locks.
Examples include:
TX TM UL
So:
GCS → blocks
GES → locks/enqueues
This distinction is fundamental for RAC troubleshooting.
2. What Is an Enqueue?
An enqueue is a locking mechanism used by Oracle to coordinate access to resources.
For example:
TX → Transaction enqueue TM → DML enqueue
A simplified example:
Session A | | UPDATE v Row X | | locked v Session B | | UPDATE same row v WAIT
The second session must wait until the first transaction releases the resource.
3. Local vs Global Locking
In a single-instance database:
Instance | +-- Session A | +-- Session B | +-- Lock Manager
In RAC:
RAC
+-----------+
| |
ORCL1 ORCL2
| |
Session A Session B
| |
+-----+-----+
|
Global coordination
|
GES
This is why RAC locking can be more complex.
4. A Simple RAC Blocking Scenario
Imagine:
ORCL1
Session 101
|
| UPDATE CUSTOMER
|
v
Customer ID = 100
^
|
| UPDATE CUSTOMER
|
Session 205
ORCL2
Session 101 modifies the row but does not commit.
Session 205 attempts to modify the same row.
Result:
ORCL2 Session 205
|
↓
WAITING
|
↓
ORCL1 Session 101
|
↓
BLOCKER
This is a cross-instance blocking situation.
5. First Tool: GV$SESSION
When investigating blocking, start with:
SELECT inst_id,
sid,
serial#,
username,
status,
event,
sql_id,
blocking_instance,
blocking_session,
seconds_in_wait
FROM gv$session
WHERE blocking_session IS NOT NULL
ORDER BY seconds_in_wait DESC;
This can immediately reveal:
INST_ID SID BLOCKING_INSTANCE BLOCKING_SESSION ------- ---- ----------------- ----------------- 2 205 1 101
Meaning:
ORCL2 / SID 205
↓
blocked by
↓
ORCL1 / SID 101
6. Find the Blocker
Now query the blocking session:
SELECT inst_id,
sid,
serial#,
username,
status,
machine,
program,
module,
service_name,
sql_id,
event,
logon_time
FROM gv$session
WHERE sid = 101
AND inst_id = 1;
Don’t stop at the SID.
You want to understand:
- Who owns the session?
- Which application?
- Which service?
- Which machine?
- Which SQL?
- How long has it been connected?
7. The Most Important Question
Finding the blocker is only step one.
You need to ask:
Why hasn’t the blocker committed or rolled back?
For example:
Session 101 | +-- transaction started 45 minutes ago | +-- application waiting | +-- client disconnected | +-- transaction still open
This is a classic production problem.
8. Find Transaction Information
You can correlate sessions with transactions:
SELECT s.inst_id,
s.sid,
s.serial#,
s.username,
s.status,
s.sql_id,
t.start_date,
t.start_time
FROM gv$session s
JOIN gv$transaction t
ON s.taddr = t.addr
AND s.inst_id = t.inst_id
ORDER BY t.start_date;
Depending on your Oracle release/view definitions, transaction timing columns can differ, so verify the available columns in your environment.
9. A Better Transaction View
Useful transaction information includes:
SELECT inst_id,
addr,
xidusn,
xidslot,
xidsqn,
start_time,
used_ublk,
used_urec
FROM gv$transaction
ORDER BY start_time;
This can tell you:
- Transaction age
- Undo usage
- Transaction identity
A transaction running for a very long time deserves investigation.
10. Why Long Transactions Are Dangerous
Suppose:
09:00 Session A starts transaction 09:01 UPDATE customer 09:05 Another session needs same row 09:10 Still waiting 09:30 Still waiting
The problem isn’t necessarily RAC.
The problem may simply be:
An application transaction remained open for 30 minutes.
RAC only makes the coordination between instances more complex.
11. GV$LOCK
Another important view is:
SELECT inst_id,
sid,
type,
id1,
id2,
lmode,
request,
block
FROM gv$lock
ORDER BY inst_id, sid;
Important columns:
LMODE
Current lock mode.
REQUEST
Requested lock mode.
BLOCK
Whether the session is blocking another session.
12. Understanding LMODE
Common lock modes include:
0 = None 1 = Null 2 = Row-S 3 = Row-X 4 = Share 5 = S/Row-X 6 = Exclusive
For example:
LMODE = 6
means:
Exclusive lock.
13. Find Current Blockers
A quick query:
SELECT inst_id,
sid,
type,
id1,
id2,
lmode,
request,
block
FROM gv$lock
WHERE block = 1
ORDER BY inst_id, sid;
This gives you sessions currently identified as blockers at the lock level.
14. Find Sessions Waiting for Locks
SELECT inst_id,
sid,
type,
id1,
id2,
lmode,
request,
block
FROM gv$lock
WHERE request > 0
ORDER BY inst_id, sid;
A session with:
REQUEST > 0
is requesting a lock mode it doesn’t currently have.
15. Join Blocker and Waiter
A more useful diagnostic query:
SELECT
w.inst_id AS waiter_inst,
w.sid AS waiter_sid,
b.inst_id AS blocker_inst,
b.sid AS blocker_sid,
w.type,
w.id1,
w.id2,
w.request AS waiter_request,
b.lmode AS blocker_mode
FROM gv$lock w
JOIN gv$lock b
ON w.type = b.type
AND w.id1 = b.id1
AND w.id2 = b.id2
WHERE w.request > 0
AND b.block = 1;
This starts building the blocking relationship:
Waiter ↓ Resource ↓ Blocker
16. Add Session Information
Now make the report more useful:
SELECT
ws.inst_id AS waiter_inst,
ws.sid AS waiter_sid,
ws.username AS waiter_user,
ws.sql_id AS waiter_sql,
bs.inst_id AS blocker_inst,
bs.sid AS blocker_sid,
bs.username AS blocker_user,
bs.sql_id AS blocker_sql,
wl.type
FROM gv$lock wl
JOIN gv$lock bl
ON wl.type = bl.type
AND wl.id1 = bl.id1
AND wl.id2 = bl.id2
JOIN gv$session ws
ON ws.inst_id = wl.inst_id
AND ws.sid = wl.sid
JOIN gv$session bs
ON bs.inst_id = bl.inst_id
AND bs.sid = bl.sid
WHERE wl.request > 0
AND bl.block = 1;
Now you can see:
Waiter: ORCL2 / SID 205 SQL = ABC123 Blocker: ORCL1 / SID 101 SQL = XYZ789
17. TX Locks
One of the most common locks you’ll encounter during application contention is:
TX
TX is associated with transactions.
For example:
UPDATE same row
can produce TX contention.
Typical wait:
enq: TX - row lock contention
18. Recognizing TX Contention
Check:
SELECT inst_id,
sid,
serial#,
username,
event,
sql_id,
blocking_instance,
blocking_session,
seconds_in_wait
FROM gv$session
WHERE event = 'enq: TX - row lock contention'
ORDER BY seconds_in_wait DESC;
This is one of the most useful queries during an application incident.
19. TM Locks
Another important enqueue is:
TM
TM is related to table-level DML locking.
A common cause of TM contention can involve:
- DML
- Foreign keys
- Missing indexes on foreign keys
- Concurrent parent/child operations
This is particularly important in OLTP systems.
20. The Classic Foreign-Key Problem
Imagine:
PARENT ------- ID CHILD ----- PARENT_ID
If the child table doesn’t have a suitable index on:
CHILD.PARENT_ID
certain concurrent parent operations can result in serious locking problems.
Example:
CREATE INDEX child_parent_fk_i ON child(parent_id);
The exact indexing strategy depends on workload and schema design, but the principle is important:
Foreign-key indexing can be critical for concurrency.
21. Why This Becomes Worse in RAC
In a single instance:
Session A Session B ↓ Local lock manager
In RAC:
ORCL1 Session A
|
|
GES
|
|
ORCL2 Session B
The coordination is now cluster-wide.
Therefore cross-instance contention can add overhead.
22. Enqueue Waits
Check:
SELECT inst_id,
event,
COUNT(*) sessions
FROM gv$session
WHERE status = 'ACTIVE'
AND event LIKE 'enq:%'
GROUP BY inst_id, event
ORDER BY sessions DESC;
Example:
ORCL1 → enq: TX - row lock contention → 3 ORCL2 → enq: TX - row lock contention → 25
That’s an immediate signal that ORCL2 deserves attention.
23. Blocking Chains
One blocker can block many sessions.
Example:
ORCL1
Session 101
|
↓
RESOURCE
+---------+---------+
| | |
↓ ↓ ↓
ORCL2 ORCL2 ORCL2
SID 201 SID 202 SID 203
This is far more serious than a single blocked session.
The blocker becomes a root blocker.
24. Find the Root Blocker
A useful approach is to repeatedly follow:
blocked session
↓
blocking session
↓
blocking session
↓
root blocker
Oracle can expose blocking information through GV$SESSION.
For more complex chains, build a recursive diagnostic query or use ASH to reconstruct the chain historically.
25. Example Blocking Chain
Suppose:
ORCL2 SID 300
↓
blocked by
↓
ORCL1 SID 200
↓
blocked by
↓
ORCL2 SID 100
The real problem is:
ORCL2 SID 100
because killing SID 200 may only move the problem somewhere else.
This is why:
Always identify the root blocker before taking action.
26. Blocking Tree
Think about it as:
ROOT BLOCKER
|
+---- Session A
|
+---- Session B
|
+---- Session C
|
+---- Session D
The root blocker can have a huge impact.
27. ASH for Historical Blocking
If the incident happened yesterday, current GV$SESSION won’t help.
Use:
SELECT sample_time,
instance_number,
session_id,
session_serial#,
blocking_inst_id,
blocking_session,
blocking_session_serial#,
sql_id,
event
FROM dba_hist_active_sess_history
WHERE sample_time BETWEEN
TIMESTAMP '2026-08-07 09:00:00'
AND TIMESTAMP '2026-08-07 09:30:00'
AND blocking_session IS NOT NULL
ORDER BY sample_time;
This is extremely powerful.
28. Historical Blocking by Instance
SELECT instance_number,
blocking_inst_id,
blocking_session,
COUNT(*) samples
FROM dba_hist_active_sess_history
WHERE sample_time BETWEEN
TIMESTAMP '2026-08-07 09:00:00'
AND TIMESTAMP '2026-08-07 09:30:00'
AND blocking_session IS NOT NULL
GROUP BY instance_number,
blocking_inst_id,
blocking_session
ORDER BY samples DESC;
Now you can determine which instance contained the blocker.
29. Find the SQL of the Blocker
Once you know the blocking SID, query ASH:
SELECT sample_time,
instance_number,
session_id,
sql_id,
event,
wait_class,
module,
service_name
FROM dba_hist_active_sess_history
WHERE instance_number = :inst_id
AND session_id = :sid
AND sample_time BETWEEN :start_time AND :end_time
ORDER BY sample_time;
Now you can see what the blocker was actually doing.
30. The Blocker May Not Be “Doing” Anything
This is a common trap.
You find:
Blocker SID 101 STATUS = INACTIVE
You might think:
“It’s inactive, so it can’t be the problem.”
Wrong.
A session can be:
INACTIVE
while still holding an uncommitted transaction.
For example:
Application:
UPDATE
↓
Transaction open
↓
Connection becomes idle
↓
No COMMIT
The session appears inactive but continues holding locks.
31. Find Idle Sessions Holding Transactions
A useful investigation:
SELECT s.inst_id,
s.sid,
s.serial#,
s.username,
s.status,
s.last_call_et,
t.start_time,
t.used_ublk
FROM gv$session s
JOIN gv$transaction t
ON s.taddr = t.addr
AND s.inst_id = t.inst_id
WHERE s.status = 'INACTIVE'
ORDER BY t.start_time;
This can identify suspicious idle transactions.
32. Application Connection Pools
This is common in modern applications.
Example:
Application
|
Connection Pool
|
+-- Connection 1
+-- Connection 2
+-- Connection 3
A connection may remain open while a transaction remains active.
Therefore:
Connection pool configuration and transaction handling are part of Oracle performance troubleshooting.
Not everything is a database problem.
33. Deadlocks
Now we reach a more serious situation.
A deadlock occurs when:
Session A waits for Session B Session B waits for Session A
Diagram:
Session A | | waits for v Resource B ^ | held by | Session B | | waits for v Resource A
There is no way forward.
Oracle detects the cycle.
34. ORA-00060
A typical error is:
ORA-00060: deadlock detected while waiting for resource
In RAC, the situation can span instances.
Example:
ORCL1 Session A
|
↓
Resource X
↑
|
ORCL2 Session B
|
↓
Resource Y
↑
|
ORCL1 Session A
Oracle must detect the global dependency cycle.
35. Deadlock ≠ Blocking
This distinction is critical.
Blocking
A → waits for B
Eventually B commits.
Deadlock
A → waits for B B → waits for A
Neither can proceed.
So:
Blocking can be normal. Deadlocks are always a problem to investigate.
36. Finding Deadlock Information
When Oracle detects a deadlock, it generates diagnostic information.
Check the alert log and trace files.
For example:
adrci
Then:
show alert
or inspect the relevant diagnostic trace.
The trace can contain a deadlock graph.
37. Deadlock Graph
A simplified graph might look like:
---------Blocker(s)-------- Resource TX-12345 Session 100 Instance 1 ---------Waiter(s)--------- Resource TX-67890 Session 200 Instance 2
The full trace provides the relationships needed to identify the application transactions involved.
38. Common Deadlock Cause
A classic application pattern:
Transaction A
UPDATE CUSTOMER UPDATE ORDER COMMIT
Transaction B
UPDATE ORDER UPDATE CUSTOMER COMMIT
If they run concurrently:
A locks CUSTOMER B locks ORDER A waits for ORDER B waits for CUSTOMER
Deadlock.
39. The Solution Is Usually Application Design
The solution isn’t:
Increase SGA Increase PGA Increase processes
Instead:
Make transactions acquire resources in a consistent order.
For example:
Always: CUSTOMER ↓ ORDER
instead of:
Transaction A: CUSTOMER → ORDER Transaction B: ORDER → CUSTOMER
Consistent locking order dramatically reduces deadlock risk.
40. RAC Makes Deadlocks More Interesting
A cross-instance deadlock may look like:
ORCL1 Session 100 | | holds TX A ↓ waits for TX B ↑ | ORCL2 Session 200 | | holds TX B ↓ waits for TX A
GES coordinates the global resource relationships.
This is one reason RAC DBAs need to understand both:
GCS
and:
GES
41. GES Monitoring Views
Depending on your Oracle 19c environment, useful RAC diagnostic views include:
GV$GES_RESOURCE GV$GES_BLOCKING_ENQUEUE GV$GES_ENQUEUE GV$GES_STATISTICS
You should always verify exact columns in your installation:
DESC GV$GES_RESOURCE; DESC GV$GES_BLOCKING_ENQUEUE; DESC GV$GES_ENQUEUE;
Don’t build production scripts based on assumed column names.
42. Inspect GES Resources
For example:
SELECT * FROM gv$ges_resource;
For a targeted investigation, filter the view based on the columns available in your environment.
The objective is to understand:
Resource Owner Requesters Instance Lock state
43. GES Statistics
You can inspect global enqueue activity with:
SELECT inst_id,
*
FROM gv$ges_statistics;
Because the exact output can be extensive, use:
DESC GV$GES_STATISTICS;
first, then select only the relevant counters.
This is a good habit for expert-level Oracle administration:
Discover the view structure instead of assuming it.
44. A Practical RAC Lock Diagnostic Script
Here is a useful starting point:
SET LINESIZE 250
SET PAGESIZE 100
COLUMN waiter FORMAT A15
COLUMN blocker FORMAT A15
COLUMN event FORMAT A35
COLUMN sql_id FORMAT A15
SELECT
ws.inst_id || ':' || ws.sid AS waiter,
bs.inst_id || ':' || bs.sid AS blocker,
ws.username AS waiter_user,
bs.username AS blocker_user,
ws.event,
ws.sql_id AS waiter_sql,
bs.sql_id AS blocker_sql,
ws.seconds_in_wait
FROM gv$session ws
LEFT JOIN gv$session bs
ON bs.inst_id = ws.blocking_instance
AND bs.sid = ws.blocking_session
WHERE ws.blocking_session IS NOT NULL
ORDER BY ws.seconds_in_wait DESC;
This is something you can keep in your RAC DBA toolkit.
45. A More Complete Blocking Report
Add application information:
SELECT
ws.inst_id || ':' || ws.sid AS waiter,
bs.inst_id || ':' || bs.sid AS blocker,
ws.username AS waiter_user,
bs.username AS blocker_user,
ws.service_name AS waiter_service,
bs.service_name AS blocker_service,
ws.module AS waiter_module,
bs.module AS blocker_module,
ws.sql_id AS waiter_sql,
bs.sql_id AS blocker_sql,
ws.event,
ws.seconds_in_wait
FROM gv$session ws
LEFT JOIN gv$session bs
ON bs.inst_id = ws.blocking_instance
AND bs.sid = ws.blocking_session
WHERE ws.blocking_session IS NOT NULL
ORDER BY ws.seconds_in_wait DESC;
This can quickly tell you:
Application A
↓
blocked by
↓
Batch Application
which is much more actionable.
46. Before Killing a Session
This is extremely important.
Never blindly run:
ALTER SYSTEM KILL SESSION 'sid,serial#,@inst';
just because a session is blocking others.
First determine:
- Who owns the session?
- Which application?
- Is it a critical transaction?
- How long has it been running?
- What transaction is active?
- How many sessions are blocked?
- Is rollback going to be expensive?
- Is the session already disconnected from the application?
- Is there a safer application-side solution?
47. If You Must Kill It
In RAC, the syntax includes the instance:
ALTER SYSTEM KILL SESSION '101,12345,@1';
The exact action should be based on your operational procedures.
Possible alternatives include:
DISCONNECT SESSION
or allowing the application to resolve the transaction.
The key point is:
Killing the blocker is an operational decision, not a diagnostic shortcut.
48. Why Killing a Session Can Make Things Worse
Suppose:
Transaction: UPDATE 2 million rows
You kill it.
Oracle may need to roll back a large transaction.
That rollback can:
- Consume CPU
- Generate I/O
- Generate undo activity
- Take significant time
- Continue affecting application performance
So:
A session that is blocking users may also be holding a large transaction whose rollback is expensive.
Always investigate before acting.
49. A Production Troubleshooting Sequence
When users report:
“The application is hanging.”
I recommend:
1. Check GV$SESSION
↓
2. Find waiters
↓
3. Find blockers
↓
4. Identify root blocker
↓
5. Identify SQL
↓
6. Identify transaction
↓
7. Identify application/module
↓
8. Identify affected object
↓
9. Check RAC instance
↓
10. Decide corrective action
50. Example: Real RAC Investigation
Imagine this output:
WAITING SESSION Instance: 2 SID: 500 Service: SALES_APP SQL_ID: 9abc123 Event: enq: TX - row lock contention Wait: 1,200 sec
The blocker:
Instance: 1 SID: 200 Service: BATCH SQL_ID: 4xyz789 Status: INACTIVE
At first:
“Batch session is inactive.”
But transaction information shows:
Transaction age: 45 minutes Undo blocks: 800,000
Now the root cause is much clearer:
Batch ↓ Long-running uncommitted transaction ↓ Locks rows ↓ SALES_APP waits ↓ Cross-instance contention
This is an application/workload management problem.
51. How to Explain This to Management
Don’t say:
“RAC is causing blocking.”
That’s technically misleading.
A better incident statement is:
“The SALES application experienced transaction lock contention because a long-running batch transaction on the first RAC instance retained row locks required by OLTP sessions on the second instance.”
That’s the language of a senior DBA.
52. RAC Locking Checklist
During a locking incident:
Session
- SID
- SERIAL#
- INST_ID
- USERNAME
- MACHINE
- PROGRAM
- MODULE
- SERVICE
SQL
- SQL_ID
- SQL text
- Execution plan
- Duration
- Executions
Lock
- Lock type
- ID1
- ID2
- LMODE
- REQUEST
- BLOCK
Transaction
- Start time
- Undo usage
- Transaction age
RAC
- Local or remote blocker
- Instance
- Service
- GES activity
Application
- Batch?
- OLTP?
- Connection pool?
- Missing commit?
- Long transaction?
53. Expert Mental Model
At this stage, your RAC architecture should look like this:
APPLICATION
|
SERVICE
|
+------------+------------+
| |
ORCL1 ORCL2
| |
SESSION A SESSION B
| |
TX / TM TX / TM
| |
+-----------+-------------+
|
GES
|
Global Enqueue Coordination
And for data blocks:
SESSION
|
SQL
|
BLOCK
|
GCS
|
Cache Fusion
Therefore:
LOCK PROBLEM
↓
GES
BLOCK TRANSFER
↓
GCS
That distinction will save you a lot of troubleshooting time.
54. Part 17 Summary
Today we covered:
- GES architecture
- GCS vs GES
- RAC global locking
- TX enqueues
- TM enqueues
GV$SESSIONGV$LOCK- Blocking sessions
- Root blockers
- Long-running transactions
- Idle sessions holding locks
- Cross-instance blocking
- Historical blocking with ASH
- Deadlocks
- ORA-00060
- GES diagnostic views
- Production-safe session termination
- Building a RAC blocking report
The key principle is:
Don’t just find the blocked session. Find the transaction that owns the resource, understand why it remains open, and determine why the application created the contention.


