Oracle problems
library cache lock, mutex X and cursor: pin S wait on X: The Fix
3 November 2026
Three waits with the words library cache or cursor in them, and one cause behind almost all of them: parsing. library cache lock is a session that wants an object another session holds, usually because that session is changing it, compiling it or granting on it. library cache: mutex X is a hash bucket of cursors held while a statement is loaded, which Oracle's reference says "is often associated with high parse activity". cursor: pin S wait on X is a session that wants to run a cursor another session is still building. None of them is fixed by memory alone. The queries below name the holder, the object and the parse rate, and the fix is a piece of DDL moved out of the day, a bind variable, or a cursor that stops being rebuilt.
What the waits mean
The reference describes library cache lock as the event that "controls the concurrency between clients of the library cache": a client acquires a lock on the object handle so that "one client can prevent other clients from accessing the same object", or so that "the client can maintain a dependency for a long time (for example, no other client can change the object)", and the lock "is also obtained to locate an object in the library cache". Its wait time is 3 seconds. For library cache: mutex X: "Library cache mutexes protect hash buckets within the shared pool. Each hash bucket contains a number of cursors. The mutex must be held in exclusive mode before any of the structures that it protects can be changed." And cursor: pin S wait on X: "A session waits for this event when it is requesting a shared mutex pin and another session is holding an exclusive mutex pin on the same cursor object." Its P2 carries the holder: the top two bytes are the SID of the session that holds the mutex in exclusive mode, which is the session doing the hard parse.
Who holds it, and on what
SELECT s.sid, s.event, s.blocking_session,
CASE WHEN s.event = 'cursor: pin S wait on X'
THEN TRUNC(s.p2 / 65536) END AS mutex_holder,
s.p1raw AS handle_or_hash,
ROUND(s.wait_time_micro / 1e6) AS waited_s,
s.sql_id, s.username, s.program
FROM v$session s
WHERE s.event IN ('library cache lock', 'library cache: mutex X',
'cursor: pin S wait on X')
ORDER BY waited_s DESC;
For library cache lock, blocking_session names the holder, and the holder's own row in V$SESSION says what it is doing: an ALTER TABLE, a package compile, a GRANT, a statistics gather, or nothing at all, idle, with the lock held by a transaction it has not ended. For cursor: pin S wait on X the holder is mutex_holder, and its sql_id is the statement being parsed. The cursor itself, from its hash value:
SELECT owner, name, namespace, type, sharable_mem, loads,
executions, locks, pins, invalidations
FROM v$db_object_cache
WHERE hash_value = &p1;
loads climbing against few executions is a cursor that is rebuilt over and over, and invalidations says why: a DDL or a statistics change on something it depends on throws every session back into a hard parse of the same statement at the same moment.
How much parsing there is
SELECT name, value
FROM v$sysstat
WHERE name IN ('parse count (total)', 'parse count (hard)',
'parse count (failures)', 'parse time elapsed',
'session cursor cache hits', 'execute count');
Two readings a minute apart give the rate. Hard parses close to total parses is literal SQL, no bind variables, which the shared pool page covers in full. Failures climbing is an application retrying a statement that errors on parse, which costs a hard parse every time and never shows in the slow-query lists. Then the cursors that exist in too many versions, each one a separate child that every new session has to search through under the mutex:
SELECT sql_id, version_count, loaded_versions, executions,
invalidations, last_active_time
FROM v$sqlarea
WHERE version_count > 50
ORDER BY version_count DESC FETCH FIRST 10 ROWS ONLY;
V$SQL_SHARED_CURSOR has one column for every reason a child could not be shared, and the columns that read Y for the top sql_id name the mismatch: a bind of a different length or type from each client, a different optimizer environment per session, a different language setting.
The fix, in order
- Move the DDL out of the day. A grant, a package compile, an index rebuild, a
TRUNCATEor anALTER TABLEon an object that a hundred sessions execute invalidates their cursors and takes the lock they all need. The same change at two in the morning takes the same lock with nobody behind it. A statistics gather with the defaultAUTO_INVALIDATEspreads the invalidations over time; one withNO_INVALIDATE => FALSEinvalidates everything at once. - End the holder that is not doing anything. A session that holds a library cache lock while idle is a transaction that did DDL, or a compile, and stopped;
ALTER SYSTEM KILL SESSIONreleases it and the queue drains. - Bind variables, or CURSOR_SHARING as the stopgap. Literal SQL hard-parses every execution and holds the mutex for each one. The application change is the fix;
CURSOR_SHARING = FORCEbuys the time to make it. - Cut the version count. Fix the mismatch
V$SQL_SHARED_CURSORnames: declare binds with one length and type from every client, keep the optimizer and session settings the same across the pool, and flush the one bloated cursor withDBMS_SHARED_POOL.PURGEwhile the fix ships. - Cache the cursors. A higher
SESSION_CACHED_CURSORSkeeps a soft parse from walking the library cache at all, and an application that holds its prepared statements open instead of closing and re-preparing them stops asking in the first place. - Fix the statement that fails to parse.
parse count (failures)rising at a steady rate is one statement with a syntax error, a missing privilege or a dropped object that the application retries forever; the alert log and the application log name it. - Then, and only then, memory. A shared pool that is too small ages cursors out and reloads them, which is a hard parse with a mutex for each reload; the ORA-04031 page has the sizing, and it comes after the parse rate, not before.
See it coming
Radar Engine, the detection layer of Scops, reads the parse counters and the shared pool every 5 minutes and the sessions every 5 seconds, so a parse storm is a finding with the statement and the rate while the first sessions queue on the mutex:
| Detector | Clock | When it fires |
|---|---|---|
| Hard parse storm | Live | 30 hard parses a second, far above the hour before, with shared pool problems: Warning. Five times the usual and 90 a second: Critical. |
| Shared pool draining | Live, every 5 minutes | Free memory decreases: Warning. 3% or less: Critical. |
| Shared pool low on memory | Hourly, on the worst instance | Under 3%: Watch. Under 1% and well under its usual: Warning. |
| Concurrency or Application waits surging | Live, against the hour before | Twice the hour before, well above its usual spread: Watch. With a second sign, such as 5 blocked sessions: Warning. |
| Sudden wait event | Live | A sudden large share of the waits: Watch. The largest one, if it continues: Warning. |
The finding names the statement being parsed, the rate, and the session that holds the lock or the mutex with what it is running. The Activity chart draws the Concurrency class by statement and the parse counters next to it, on any edition and without the Diagnostics Pack.
Questions
What is library cache lock?
A session wants an object in the library cache that another session holds, usually because that session is changing it: DDL, a compile, a grant, or a statistics gather. V$SESSION's BLOCKING_SESSION names the holder, and the fix is to finish or end the holder and to move such changes out of the busy hours.
What is library cache: mutex X?
The mutex that protects a hash bucket of cursors in the shared pool, held in exclusive mode while a cursor is loaded or changed. Oracle's reference ties it to high parse activity: literal SQL, cursors with hundreds of versions, or a popular statement that was just invalidated.
What is cursor: pin S wait on X?
A session wants to execute a cursor that another session is still building, after a hard parse or an invalidation. The holder's SID is in the top two bytes of P2. A storm of these on one statement right after a DDL or a statistics change is the invalidation; the same storm all day is literal SQL.
How do I find who holds the library cache lock?
Query V$SESSION for the sessions on the event and read BLOCKING_SESSION; then look at the holder's own row for its status, its SQL_ID and how long it has been idle. A holder that is idle has a transaction open from a DDL or a compile and can be killed; an active one is a change in progress.
Related problems
- ORA-04031: shared pool out of memory
- SQL plan changed
- Invalid objects and unusable indexes
- enq: TX - row lock contention
- Oracle wait events, by class
- All Oracle problems
Sources
- Oracle: Descriptions of wait events, V$DB_OBJECT_CACHE, V$SQL_SHARED_CURSOR, V$SQLAREA
- Oracle: DBMS_SHARED_POOL, SESSION_CACHED_CURSORS
- Scops docs: Detector reference