DBAtlas · Database operations atlas

Slow query: in-depth diagnosis

A query or the whole system is slow. Narrow it down step by step: run a check at each node and choose the branch based on the result. You end with a root cause + fix.

What is slow?

Is one or a few specific queries slow, or the whole system? This splits the investigation into one of two tracks: 'query-focused' or 'resource/bottleneck-focused'.

  • One or a few specific queries are slow
  • The whole system is slow

Is the query running right now?

Is it a query running/stuck right now, or one that is 'generally slow'? Look at the active sessions.

SELECT sid, serial#, sql_id, last_call_et AS running_sec, event, blocking_session
FROM v$session WHERE status='ACTIVE' AND type='USER' ORDER BY last_call_et DESC;
  • Running / stuck right now
  • Generally slow; I want to look at its plan

Is the query waiting on something?

If it is active but not progressing, it is most likely waiting. Is blocking_session / wait_event populated?

SELECT sid, blocking_session, event, seconds_in_wait
FROM v$session WHERE sid=&target_sid;
  • Blocked by another session
  • Waiting on a resource (IO / log / latch)
  • No wait; actively burning CPU

Which wait dominates?

The dominant wait event tells you the type of bottleneck.

SELECT event, total_waits, time_waited FROM v$session_event WHERE sid=&target_sid ORDER BY time_waited DESC FETCH FIRST 5 ROWS ONLY;
  • IO wait (db file read / PAGEIOLATCH)
  • Log / commit wait (log file sync / WRITELOG)
  • Lock / enqueue (enq: TX / LCK)

Get the execution plan (EXPLAIN)

Pull the plan. Is there a full table scan, an unexpected join order, or a gap between estimated and actual rows?

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));
  • Full scan / missing index
  • It was fast yesterday; the plan changed suddenly
  • Processes too many rows / N+1
  • The plan looks good but it is slow

Are statistics up to date?

A large gap between estimated and actual rows (E-rows vs A-rows) is a sign of stale statistics.

SELECT table_name, num_rows, last_analyzed FROM dba_tables WHERE table_name='&TABLE_NAME';
  • Statistics are stale / old
  • Statistics are current but it is still slow

System-wide: where is the bottleneck?

If everything is slow rather than a single query, look for a resource bottleneck: CPU, IO, memory, or widespread blocking?

SELECT metric_name, value FROM v$sysmetric WHERE metric_name IN ('Host CPU Utilization (%)','Average Active Sessions') AND intsize_csec=(SELECT MAX(intsize_csec) FROM v$sysmetric);
  • CPU close to 100%
  • High IO wait
  • Memory pressure / low cache hit
  • Lots of blocking

Root cause: Blocking

The query is waiting on a lock held by another session. Find the session at the head of the chain (root blocker) and resolve it.

Root cause: IO bottleneck

The query reads a lot from disk. Usually due to full scans (missing index) or insufficient memory (buffer cache).

Root cause: Commit / log bottleneck

If 'log file sync' / 'WRITELOG' dominates, commits are waiting on redo/WAL writes.

Root cause: Missing index

The plan does a full scan. A suitable index on filter/join columns gives a large speedup.

Root cause: Plan regression

The query was fast yesterday but not today; the plan changed. Usually stale statistics or a parameter change.

Root cause: Query design

The plan is reasonable but the query inherently does too much work (too many rows, N+1, unnecessary joins).

Root cause: Stale statistics

The optimizer misestimates because statistics are not up to date.

In-depth analysis

If the plan and statistics are fine but it is still slow, dig deeper.

Root cause: CPU bottleneck

The whole system is CPU-bound. Usually a few badly planned queries are burning the CPU.

Root cause: Memory pressure

Low cache hit and/or swapping: memory is insufficient or misallocated.

Open in DBAtlas →