When library cache: mutex X shows up as a dominant wait event, the database is burning CPU on parsing rather than on query execution. Sessions serialize behind exclusive mutexes that protect the shared SQL area, throughput erodes, and the instance still reports OPEN/ACTIVE on every basic availability check. The shared SQL cache cannot keep up with the rate of new SQL text the application is sending.
This pattern almost always appears alongside elevated parse count (hard), declining shared pool free memory, and the sibling waits cursor: pin S wait on X and latch: shared pool. If the rate is high enough and sustained, shared pool fragmentation follows and ORA-04031 becomes a real risk.
What this means
library cache: mutex X is a concurrency wait on the shared pool, not a storage or locking problem. When a session wants to add, invalidate, or modify a library cache object (a cursor, its plan, or its metadata), it must acquire a mutex protecting the relevant hash bucket in exclusive mode. Other sessions that need to look up, pin, or invalidate cursors mapped to the same bucket wait until the holder releases.
The wait becomes a problem when the rate of exclusive operations is high enough that the queue of waiters grows faster than it drains. Two patterns dominate:
- High hard parse rate. Every unique SQL text requires a hard parse, which acquires exclusive mutexes to compile, store, and optimize the new cursor. Literal SQL (no bind variables) at high call rates saturates the bucket mutexes even when individual parses are fast.
- High child-cursor version counts. When one parent cursor accumulates many child cursors (adaptive cursor sharing, bind peeking, mismatched session environments, statistics churn), every soft parse walks a longer chain, holding mutexes longer per lookup and increasing collision probability.
cursor: pin S wait on X is the closely related sibling: a session wants to pin an existing cursor in shared mode but another session is holding it in exclusive mode for loading or invalidation. latch: shared pool appears when contention spreads to shared pool memory allocation. Seeing all three together is a strong signal that the root cause is shared SQL area pressure.
A healthy OLTP system keeps hard parses well below 1% of total parses. When the ratio crosses 5%, library cache mutex contention becomes structurally likely at scale.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Literal SQL (no bind variables) | V$SQL shows many near-identical statements differing only in literal values; each has EXECUTIONS = 1 | SELECT SQL_TEXT FROM V$SQL WHERE EXECUTIONS = 1 ORDER BY LAST_ACTIVE_TIME DESC |
| High child-cursor version count | Few parent SQL_IDs with many child cursors; cursor: pin S wait on X correlated with mutex X | V$SQL_SHARED_CURSOR for the offending SQL_ID |
| Shared pool flush | Sudden spike in hard parses after a manual ALTER SYSTEM FLUSH SHARED_POOL | Alert log or DBA activity around onset time |
| Shared pool undersized | Free memory in shared pool under 5%, frequent ASMM resize oscillation | V$SGASTAT for shared pool free memory |
| Adaptive Cursor Sharing churn | Many BIND_EQUIV_FAILURE reasons in V$SQL_SHARED_CURSOR, often after upgrade | Check current RU for known ACS or Cardinality Feedback bugs |
| Cursor invalidations from DDL/stats | V$SQL.INVALIDATIONS climbing on hot SQL | DBMS_STATS schedule and DDL audit trail |
Quick checks
All queries below are read-only and safe during an incident.
-- Confirm hard parse rate. Sample twice, 60 seconds apart.
SELECT NAME, VALUE FROM V$SYSSTAT WHERE NAME LIKE 'parse count%';
-- Hard parse ratio target: below 1% of total. Above 5% is a structural problem.
-- Library cache mutex wait profile.
SELECT EVENT, TOTAL_WAITS, TIME_WAITED_MICRO,
ROUND(TIME_WAITED_MICRO/NULLIF(TOTAL_WAITS,0)/1000, 2) AS avg_ms
FROM V$SYSTEM_EVENT
WHERE EVENT IN (
'library cache: mutex X',
'cursor: pin S wait on X',
'cursor: mutex S',
'cursor: mutex X',
'latch: shared pool'
)
ORDER BY TIME_WAITED_MICRO DESC;
-- Current waiters, right now.
SELECT SID, SERIAL#, EVENT, SECONDS_IN_WAIT, SQL_ID, BLOCKING_SESSION
FROM V$SESSION
WHERE EVENT IN ('library cache: mutex X', 'cursor: pin S wait on X')
AND STATE = 'WAITING'
ORDER BY SECONDS_IN_WAIT DESC;
-- Shared pool free memory. Below 5% of pool is a red flag.
SELECT POOL, NAME, BYTES/1048576 AS mb
FROM V$SGASTAT
WHERE POOL = 'shared pool' AND NAME = 'free memory';
-- Look for one-shot literal statements.
-- FETCH FIRST requires 12c+. Use ROWNUM <= 20 on 11g.
SELECT SQL_ID, EXECUTIONS, ELAPSED_TIME, BUFFER_GETS, SUBSTR(SQL_TEXT,1,80) AS sql_text
FROM V$SQL
WHERE EXECUTIONS = 1
ORDER BY LAST_ACTIVE_TIME DESC
FETCH FIRST 20 ROWS ONLY;
-- Parent cursors with many children (version-count problem).
SELECT SQL_ID, COUNT(*) AS child_count
FROM V$SQL
GROUP BY SQL_ID
HAVING COUNT(*) > 10
ORDER BY child_count DESC;
V$ACTIVE_SESSION_HISTORY and AWR are Diagnostics Pack features (Enterprise Edition only). Do not query them without that license.
How to diagnose it
- Confirm the wait is actually library cache mutex X. Start with the wait profile query above. If
library cache: mutex Xand its sibling events account for more than 5% of non-idle DB time, the diagnosis fits. If they are below noise, look elsewhere: CPU saturation,enq: TX, orlog file sync. - Quantify the hard parse rate. Sample
parse count (total)andparse count (hard)twice, 60 seconds apart. Computehard / total * 100. Above 5% is structural; above 20% is severe. Compare against CPU utilization: high CPU with high hard parse rate and library cache mutex waits is the textbook Parse Storm signature. - Distinguish literal SQL from version-count explosion.
- If
V$SQLshows many unique SQL_TEXTs withEXECUTIONS = 1, the root cause is literal SQL. The application is sending new text for every call. - If a small number of SQL_IDs each have dozens or hundreds of child cursors, the root cause is version count. The parent cursor is being shared, but every session generates a new child.
- If
- For version-count problems, identify the non-sharing reason.
V$SQL_SHARED_CURSORexposes why Oracle refused to reuse an existing child. Common reasons:BIND_EQUIV_FAILURE(adaptive cursor sharing),OPTIMIZER_MISMATCH,LANGUAGE_MISMATCH,TRANSLATION_MISMATCH. Each points to a different fix. - Check for recent changes. Was
CURSOR_SHARINGmodified? Was the shared pool flushed? Did a new build deploy? DidDBMS_STATSrun? Did an upgrade land recently? Some 19c RUs have introducedBIND_EQUIV_FAILUREchurn from Adaptive Cursor Sharing and Cardinality Feedback. - Correlate with shared pool health. Falling free memory plus rising mutex waits plus eventual ORA-04031 is the cascade. Catch it before ORA-04031, because once it starts, fix attempts become harder.
flowchart TD
A[library cache mutex X dominant] --> B{Hard parse ratio above 5%}
B -- Yes --> C[Literal SQL pattern]
B -- No --> D{High child cursor counts}
C --> E[V$SQL EXECUTIONS = 1, near-identical text]
D -- Yes --> F[V$SQL_SHARED_CURSOR reasons]
D -- No --> G[Check invalidations, shared pool size]
E --> H[CURSOR_SHARING FORCE short term, binds long term]
F --> I[Fix session env, ACS, stats churn]
G --> J[Address invalidation source, resize shared pool]Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
parse count (hard) / parse count (total) ratio | Direct measure of how often the optimizer is invoked from scratch | Ratio above 1% sustained, above 5% severe |
| Hard parses per second | Absolute parse pressure | Above 100/sec is common contention territory for OLTP |
library cache: mutex X, cursor: pin S wait on X wait time | The wait events themselves | Above 5% of non-idle DB time |
latch: shared pool wait time | Shared pool memory allocation contention | Any sustained presence |
| Shared pool free memory | Headroom before ORA-04031 | Below 5% of pool size |
V$SQL.INVALIDATIONS | Cursor invalidation rate, a parse storm trigger | Climbing on hot SQL_IDs |
| Child cursor count per parent | Version-count pressure | Above 10 children per parent for hot SQL |
V$SGA_RESIZE_OPS oscillation | ASMM robbing buffer cache to feed shared pool | Frequent back-and-forth resizes |
| CPU utilization | Hard parsing is CPU-intensive | High CPU plus high hard parse rate |
| ORA-04031 in alert log | Shared pool OOM has begun | Any occurrence |
Fixes
Literal SQL (the most common cause)
Short term, while the application is being fixed:
-- Side effects possible. Test before applying in production.
ALTER SYSTEM SET CURSOR_SHARING = FORCE;
CURSOR_SHARING = FORCE rewrites literals in incoming SQL into system-generated bind variables, collapsing many unique texts into one. It can change plan quality for the worse in edge cases (bind peeking) and is not a permanent fix. The application should be modified to use real bind variables. Do not use CURSOR_SHARING = SIMILAR: it has been deprecated since 11.2.
High child-cursor version counts
Read V$SQL_SHARED_CURSOR for the offending SQL_ID and act on the most common reason:
BIND_EQUIV_FAILURE: adaptive cursor sharing is generating a new child for every bind set. Investigate whether ACS is needed at all for this SQL; in some 19c RUs the right move is a one-off_fix_controlafter confirming the bug with Oracle Support.OPTIMIZER_MISMATCH: sessions are using different optimizer parameters or features. Standardize the session environment via logon trigger or application connection settings.LANGUAGE_MISMATCH/TRANSLATION_MISMATCH: clients are using different NLS settings. Standardize NLS at the connection pool.- Statistics-driven churn: review whether
DBMS_STATSis running too aggressively or producing unstable histograms.
For a small set of extremely hot cursors, DBMS_SHARED_POOL.MARKHOT is an option in 11.2.0.3+. It tells Oracle to maintain multiple copies of the cursor to spread mutex load. It is a targeted intervention, not a blanket fix. It has documented gotchas: you must use the correct namespace (namespace => 0 for SQLAREA cursors) and the hash value from X$KGLOB, not the SQL_ID. Applying it during partition exchange operations can make things worse. Confirm with Oracle Support for your specific SQL pattern and version.
Shared pool pressure (sizing and ASMM)
If free memory is consistently under 5% and ASMM is oscillating between buffer cache and shared pool, increase SGA_TARGET or raise the shared pool minimum. Do not flush the shared pool as a routine remedy; ALTER SYSTEM FLUSH SHARED_POOL triggers a hard parse storm and is almost never the right action.
Recent-upgrade regressions
If mutex contention appeared shortly after a database upgrade, check whether Adaptive Cursor Sharing or Cardinality Feedback is generating excessive child cursors. Known bugs in this category have been documented across 12c to 19c upgrades; the right fix is the patch or _fix_control recommended by Oracle Support for your RU, not disabling adaptive features system-wide without analysis.
Prevention
- Bind variables everywhere. Make this a code-review gate. ORMs that emit literal SQL by default are the single largest preventable source of library cache mutex contention.
- Trend the hard parse ratio. Alert on ratio above 1% sustained. The cost of catching this early is trivial; the cost of catching it during an ORA-04031 cascade is not.
- Track child cursor counts for top SQL. A parent cursor that suddenly grows from 4 children to 200 is the earliest signal of ACS or environment-mismatch problems.
- Monitor shared pool free memory and SGA resize oscillation as leading indicators of ORA-04031.
- Treat
CURSOR_SHARING = FORCEas a workaround, not a strategy. It has plan-quality side effects and masks the underlying application issue. - Never flush the shared pool on a schedule. The hard parse storm it causes is worse than the fragmentation it claims to fix.
- Standardize session NLS and optimizer environments in connection pool configuration to prevent version-count explosions.
How Netdata helps
- Per-second hard parse rate and parse ratio derived from
V$SYSSTAT, so the parse storm is visible before it becomes a saturation event. - Library cache mutex wait time series (
library cache: mutex X,cursor: pin S wait on X,cursor: mutex S,cursor: mutex X,latch: shared pool) on the same timeline as CPU and shared pool free memory, so the correlation is visible without manual ASH digging. - Alerting on hard parse ratio above 1% sustained, shared pool free memory below 5%, and
library cache: mutex Xrising into the top non-idle waits, aligned to playbook thresholds. - Shared pool and buffer cache resize history from
V$SGA_DYNAMIC_COMPONENTS, surfacing ASMM oscillation that often precedes ORA-04031. - ML-based anomaly detection on parse rate and wait event distributions, which can surface slow drift toward version-count explosion before thresholds trip.
- Alert log scraping that surfaces ORA-04031 the moment it appears, alongside the wait-event and parse-rate context that explains why.
These signals are available in Oracle Database monitoring with Netdata.
Related guides
- How Oracle Database actually works in production: a mental model for operators
- Oracle archive log destination full: V$ARCHIVE_DEST_STATUS, the ERROR state, and space
- Oracle autoextend hit MAXSIZE: the space gotcha with a half-empty filesystem
- Oracle ‘Thread N cannot allocate new log’: the archive hang that masquerades as up
- Oracle ‘Checkpoint not complete’: redo log sizing, DBWn, and log-switch stalls
- Oracle ’enq: TM - contention’: unindexed foreign keys and table-level locks
- Oracle ’enq: TX - row lock contention’: blocking sessions and uncommitted DML
- Oracle Fast Recovery Area full: db_recovery_file_dest_size, reclaimable space, and DELETE OBSOLETE
- Oracle ’log file sync’ waits: slow commits, LGWR, and the redo path
- Oracle Database monitoring checklist: the signals every production instance needs
- Oracle Database monitoring maturity model: from survival to expert
- ORA-00060: deadlock detected while waiting for resource






