Symptoms
- A long report, batch job or Data Pump export fails with:
ORA-01555: snapshot too old: rollback segment number 12 with name "_SYSSMU12_..." too small
- The job worked last month and fails now that the data has grown or another batch runs at the same time.
- Retrying at night usually works.
Why it happens
Oracle gives every query a read-consistent view of the data as of the moment it started. If a row changed after that, Oracle rebuilds the older version from undo. If that undo has already been overwritten, the query can't reconstruct the old version and fails with ORA-01555.
So the error means: a query ran longer than the undo it needed was kept. Two levers fix it: make the query shorter, or keep undo longer.
Step 1: read the alert log
Oracle writes the failing statement and its duration to the alert log:
ORA-01555 caused by SQL statement below (SQL ID: 7h35uxf5uhmm1, Query Duration=10823 sec, SCN: 0x0000.1a2b3c4d):
SELECT ...
Find it with ADRCI or the V$DIAG_ALERT_EXT view:
SELECT originating_timestamp, message_text
FROM v$diag_alert_ext
WHERE message_text LIKE '%ORA-01555%'
AND originating_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
ORDER BY originating_timestamp DESC;
Note the query duration and SQL ID. That number is the retention you'd need.
Step 2: compare with what undo actually keeps
SHOW PARAMETER undo
SELECT tablespace_name, retention
FROM dba_tablespaces
WHERE contents = 'UNDO';
V$UNDOSTAT has a row per 10-minute interval for roughly the last four days:
SELECT begin_time, end_time,
tuned_undoretention, -- seconds Oracle is actually keeping undo
maxquerylen, -- longest query in the interval (seconds)
maxqueryid,
ssolderrcnt, -- ORA-01555 count
nospaceerrcnt, -- out-of-space-in-undo count
unxpstealcnt -- unexpired undo stolen
FROM v$undostat
WHERE begin_time > SYSDATE - 1
ORDER BY begin_time;
How to read it:
| Pattern | Meaning |
|---|---|
tuned_undoretention < failing query duration | Undo not kept long enough |
unxpstealcnt > 0 around the failure | Undo tablespace too small; Oracle stole unexpired undo to make space |
tuned_undoretention far above undo_retention | Autoextend lets Oracle keep more; the problem is space, not the parameter |
maxquerylen steadily growing week by week | Query or data growth; fix the query too |
Check undo tablespace size and autoextend:
SELECT file_name, bytes/1024/1024 AS mb, maxbytes/1024/1024 AS max_mb, autoextensible
FROM dba_data_files
WHERE tablespace_name = (SELECT value FROM v$parameter WHERE name = 'undo_tablespace');
Step 3: fix it
Keep undo longer
Set UNDO_RETENTION above the longest legitimate query, with margin:
ALTER SYSTEM SET undo_retention = 14400 SCOPE = BOTH; -- 4 hours
Give the undo tablespace room to honour it:
ALTER DATABASE DATAFILE '/u02/oradata/PROD/undotbs01.dbf' AUTOEXTEND ON MAXSIZE 64G;
-- or add a datafile
ALTER TABLESPACE undotbs1 ADD DATAFILE '/u02/oradata/PROD/undotbs02.dbf' SIZE 8G AUTOEXTEND ON MAXSIZE 32G;
Oracle Advisor can estimate the size needed for a target retention:
SELECT dbms_undo_adv.required_undo_size(14400) AS required_mb FROM dual;
Retention guarantee (use with care)
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;
Warning
With
RETENTION GUARANTEE, Oracle never overwrites unexpired undo. If the undo tablespace fills, DML fails with out-of-space errors instead of a report failing with ORA-01555. Only use it with plenty of undo space and monitoring, and switch back withRETENTION NOGUARANTEEif DML starts failing.
Make the query shorter
- Tune the SQL from the alert log (
SQL ID) with its execution plan; a full scan that became a 3-hour query is the root cause, not undo. - Run reports on an Active Data Guard standby, or from a snapshot or clone, instead of on the busy primary.
- Schedule the long job away from the heavy-DML batch that churns undo.
Fetch across commit
A classic self-inflicted case: a PL/SQL loop opens a cursor on a table, updates the same table and commits every N rows. Each commit lets the undo the cursor needs be reused.
-- Anti-pattern
FOR r IN (SELECT id FROM orders WHERE status = 'NEW') LOOP
UPDATE orders SET status = 'DONE' WHERE id = r.id;
IF MOD(c, 1000) = 0 THEN COMMIT; END IF;
END LOOP;
Fix it with set-based DML, a single commit at the end, or by processing in independent batches that reopen the cursor.
LOB columns
ORA-01555 on LOBs (often with ORA-22924) uses LOB retention, not the undo tablespace. For BasicFile LOBs check PCTVERSION / RETENTION; for SecureFiles check the LOB's RETENTION setting:
SELECT table_name, column_name, securefile, pctversion, retention_type
FROM dba_lobs
WHERE owner = 'APP';
Prevent it next time
- Alert on
ssolderrcnt > 0andunxpstealcnt > 0fromV$UNDOSTAT. - Track the longest report and export durations; keep
UNDO_RETENTIONat least 25% above them. - Size undo for peak DML plus retention, with autoextend and a sensible
MAXSIZE. - Run Data Pump exports with
FLASHBACK_TIME=SYSTIMESTAMPonly when undo covers the whole export, or export from a standby or clone.