ORA-01555: スナップショットが古すぎます は、Oracle が一貫性読み取りに必要な過去の UNDO データを失ったときに発生するエラーです。バッチ処理・長時間クエリ・カーソルを使った処理でよく出くわし、「なぜ発生するのか」が分かりにくいため対処に迷うことが多いエラーです。この記事では発生の仕組みから調査・対処・予防まで体系的に解説します。
- ORA-01555 が発生する仕組み(Oracle の MVCC と UNDO セグメント)
- エラーメッセージの読み方と発生パターン4つ
- V$UNDOSTAT・DBA_HIST_UNDOSTAT で発生状況を調査する方法
- UNDO_RETENTION の設定と RETENTION GUARANTEE
- fetch-across-commit パターンの回避(最も見落とされる原因)
- UNDO 表領域の適切なサイジング計算式
ORA-01555 が発生する仕組み
Oracle はマルチバージョン一貫性読み取り(MVCC)によって、クエリの開始時点のデータを一貫して見せます。別セッションが UPDATE した行を読む場合、Oracle は UNDO セグメントに保存された「更新前の値」を組み合わせてクエリ開始時点の状態を再現します。
ORA-01555 は、この再現に必要な UNDO データが上書きされてしまったときに発生します。
| 処理 | 動作 |
|---|---|
| クエリ A(長時間 SELECT)が開始 | 開始 SCN を記録(例: SCN=1000) |
| 別セッション B が UPDATE・COMMIT を繰り返す | UNDO セグメントに更新前データが蓄積される |
| UNDO セグメントが満杯になる | 古い UNDO(SCN=1000 前後のデータ)が上書きされる |
| クエリ A が上書きされた UNDO を要求 | ORA-01555 発生(SCN=1000 時点に戻れない) |
ORA-01555 はデータを更新した側ではなく、長時間 SELECT を実行している側で発生します。エラーの原因は「長時間クエリ」「UNDO 保持期間不足」「UNDO 表領域不足」のどれか(または組み合わせ)です。
エラーメッセージの読み方
ORA-01555: スナップショットが古すぎます:
ロールバック・セグメント番号 5(名前 "_SYSSMU5_3748562394$")は小さすぎます
-- または:
ORA-01555: snapshot too old: rollback segment number 5
with name "_SYSSMU5_3748562394$" is too small
- ロールバック・セグメント番号・名前:上書きされた UNDO セグメントの識別情報。「_SYSSMU」から始まる名前は自動 UNDO 管理(AUM)が管理するセグメントを意味します
- 「小さすぎます」:UNDO セグメントまたは UNDO 表領域の容量が不足していることを示します
発生パターン4つ
① UNDO_RETENTION が短すぎる
UNDO_RETENTION はUNDOデータを保持する目標時間(秒)です。デフォルトは 900 秒(15分)です。15分を超える SELECT や夜間バッチが走ると、UNDO が上書きされて ORA-01555 が発生します。
② UNDO 表領域が小さすぎる
UNDO_RETENTION を長く設定しても、UNDO 表領域の空き容量が不足するとOracle は古い UNDO を上書きせざるを得ません(RETENTION GUARANTEE なしのデフォルト動作)。
③ fetch-across-commit パターン
カーソルで FETCH しながらループ内で COMMIT する「fetch-across-commit」はORA-01555 の典型的な原因です。COMMIT により古い UNDO が解放可能になり、まだ FETCH 中のカーソルが必要とする UNDO が上書きされます。
④ AUTOEXTEND OFF で表領域が拡張できない
UNDO 表領域の AUTOEXTEND が OFF に設定されていると、容量上限に達した時点で古い UNDO が強制的に上書きされます。
発生状況の調査方法
-- V$UNDOSTAT: 直近の UNDO 統計情報(10分間隔で最大 576 行 = 4日分)
SELECT
TO_CHAR(BEGIN_TIME, 'YYYY/MM/DD HH24:MI') AS begin_time,
TO_CHAR(END_TIME, 'YYYY/MM/DD HH24:MI') AS end_time,
UNDOBLKS, -- 消費した UNDO ブロック数
TXNCOUNT, -- トランザクション数
MAXQUERYLEN, -- この期間の最長クエリ実行時間(秒)
MAXCONCURRENCY, -- 最大同時トランザクション数
SSOLDERRCNT, -- ORA-01555 の発生回数 ← ここを確認
NOSPACEERRCNT -- UNDO 容量不足エラー数
FROM V$UNDOSTAT
ORDER BY BEGIN_TIME DESC
FETCH FIRST 24 ROWS ONLY; -- 直近4時間分
-- SSOLDERRCNT > 0 の行があれば ORA-01555 が発生している
-- UNDO_RETENTION の現在値(秒)を確認
SELECT NAME, VALUE FROM V$PARAMETER
WHERE NAME = 'undo_retention';
-- デフォルト: 900(15分)
-- UNDO 表領域の使用状況
SELECT
t.tablespace_name,
t.status,
ROUND(SUM(d.bytes) / 1024 / 1024 / 1024, 2) AS total_gb,
ROUND(SUM(CASE WHEN d.autoextensible = 'YES'
THEN d.maxbytes ELSE d.bytes END)
/ 1024 / 1024 / 1024, 2) AS max_gb,
t.retention -- NOGUARANTEE / GUARANTEE
FROM DBA_TABLESPACES t
JOIN DBA_DATA_FILES d ON t.tablespace_name = d.tablespace_name
WHERE t.contents = 'UNDO'
GROUP BY t.tablespace_name, t.status, t.retention;
-- 使用中の UNDO セグメント
SELECT USN, STATUS, RSSIZE/1024/1024 AS rssize_mb, WRITES, XACTS
FROM V$ROLLSTAT
ORDER BY RSSIZE DESC;
-- DBA_HIST_UNDOSTAT: AWR が保存した過去の UNDO 統計(デフォルト8日保存)
SELECT
TO_CHAR(BEGIN_TIME, 'YYYY/MM/DD HH24:MI') AS begin_time,
MAXQUERYLEN, -- 最長クエリ時間(秒)
SSOLDERRCNT, -- ORA-01555 発生回数
UNDOBLKS,
TXNCOUNT
FROM DBA_HIST_UNDOSTAT
WHERE SSOLDERRCNT > 0 -- ORA-01555 が発生した期間のみ
ORDER BY BEGIN_TIME DESC;
対処法
① UNDO_RETENTION を増やす
-- UNDO_RETENTION を 1 時間(3600秒)に変更 ALTER SYSTEM SET UNDO_RETENTION = 3600 SCOPE = BOTH; -- SCOPE = BOTH: 即時反映 + spfile に保存 -- 夜間バッチが 4 時間かかる場合は 4 時間以上に設定 ALTER SYSTEM SET UNDO_RETENTION = 14400; -- 4時間 -- 確認 SELECT NAME, VALUE FROM V$PARAMETER WHERE NAME = 'undo_retention'; -- 注意: UNDO 表領域が小さすぎると RETENTION GUARANTEE がない場合に -- 設定値より短い保持時間になることがある(次の対処法参照)
② RETENTION GUARANTEE で UNDO を強制保護する
デフォルトでは、UNDO 表領域が満杯になると Oracle は自動的に古い UNDO を上書きします。RETENTION GUARANTEE を設定すると、UNDO_RETENTION で指定した期間が経過するまで UNDO を絶対に上書きしません。(その代わり、UNDO 表領域が枯渇すると DML が ORA-30036 エラーになります。)
-- UNDO 表領域に RETENTION GUARANTEE を設定 ALTER TABLESPACE UNDOTBS1 RETENTION GUARANTEE; -- 解除する場合 ALTER TABLESPACE UNDOTBS1 RETENTION NOGUARANTEE; -- 現在の設定確認(RETENTION 列が GUARANTEE または NOGUARANTEE) SELECT TABLESPACE_NAME, RETENTION FROM DBA_TABLESPACES WHERE CONTENTS = 'UNDO'; -- 注意: GUARANTEE を設定する前に UNDO 表領域のサイズが十分かを確認すること -- 不足した場合は ORA-30036 が発生して DML が失敗する
③ UNDO 表領域のサイジングを見直す
適切な UNDO 表領域サイズは以下の計算式で見積もります。
必要サイズ(バイト)≒ UNDO_RETENTION(秒)× UNDO 生成速度(バイト/秒)+ 余裕分UNDO 生成速度は V$UNDOSTAT の UNDOBLKS(ブロック数/10分)と DB_BLOCK_SIZE から計算できます。
-- UNDO 生成速度を計算する
SELECT
AVG(UNDOBLKS) * 8192 / 600 AS undo_bytes_per_sec, -- 8192 = ブロックサイズ(DB_BLOCK_SIZEで確認)
(SELECT VALUE FROM V$PARAMETER WHERE NAME = 'undo_retention') AS retention_sec,
AVG(UNDOBLKS) * 8192 / 600
* (SELECT VALUE FROM V$PARAMETER WHERE NAME = 'undo_retention')
/ 1024 / 1024 / 1024 AS recommended_gb
-- ※ 8192 は標準ブロックサイズ。実際の値は:
-- SELECT VALUE FROM V$PARAMETER WHERE NAME='db_block_size'
FROM V$UNDOSTAT
WHERE BEGIN_TIME >= SYSDATE - 7; -- 直近7日間の平均
-- 結果例: recommended_gb = 5.2 → 余裕を持って 10GB 以上を確保
-- UNDO 表領域にデータファイルを追加して拡張する
ALTER TABLESPACE UNDOTBS1
ADD DATAFILE '/oradata/ORCL/undotbs02.dbf' SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 30G;
④ fetch-across-commit を修正する
カーソルで FETCH しながらループ内で COMMIT するコードは ORA-01555 の典型例です。以下のパターンを避けてください。
-- BAD: カーソル FETCH 中に COMMIT している
DECLARE
CURSOR c IS SELECT employee_id, salary FROM employees;
v_id employees.employee_id%TYPE;
v_sal employees.salary%TYPE;
BEGIN
OPEN c;
LOOP
FETCH c INTO v_id, v_sal;
EXIT WHEN c%NOTFOUND;
UPDATE employees
SET salary = v_sal * 1.1
WHERE employee_id = v_id;
COMMIT; -- ← ここで COMMIT すると、カーソルが必要とする UNDO が解放可能になる
-- → 次の FETCH 時に ORA-01555 が発生する可能性がある
END LOOP;
CLOSE c;
END;
/
-- GOOD: 先にすべてのデータを取得してから更新する
DECLARE
TYPE t_emp_ids IS TABLE OF employees.employee_id%TYPE INDEX BY PLS_INTEGER;
TYPE t_salaries IS TABLE OF employees.salary%TYPE INDEX BY PLS_INTEGER;
l_ids t_emp_ids;
l_sals t_salaries;
BEGIN
-- 先にデータを全件取得(カーソルをクローズしてから DML)
SELECT employee_id, salary
BULK COLLECT INTO l_ids, l_sals
FROM employees;
-- 取得後にまとめて更新(COMMIT 時にカーソルは使っていない)
FOR i IN 1 .. l_ids.COUNT LOOP
UPDATE employees
SET salary = l_sals(i) * 1.1
WHERE employee_id = l_ids(i);
END LOOP;
COMMIT; -- FETCH は終わっているので ORA-01555 は発生しない
END;
/
-- 大量データで一括取得が難しい場合は LIMIT 句で分割処理する
FOR rec IN (SELECT ...) LOOP ... COMMIT; END LOOP; の構文も内部的に FETCH しながら COMMIT しているため、ORA-01555 が発生する可能性があります。長時間バッチでは BULK COLLECT で取得してから処理するパターンに書き換えることを推奨します。
予防策と UNDO 設定のベストプラクティス
| 項目 | 推奨設定 | 理由 |
|---|---|---|
| UNDO_RETENTION | 最長クエリ時間 × 1.5 以上 | バッファを持たせて確実に保持する |
| UNDO 表領域 AUTOEXTEND | ON(上限あり) | 突発的な UNDO 増加に対応しつつ暴走を防ぐ |
| RETENTION GUARANTEE | 本番の長時間クエリがある環境で有効化 | UNDO_RETENTION 期間を確実に保証する |
| fetch-across-commit | 禁止 | ORA-01555 と予期しないデータ不整合の原因になる |
| V$UNDOSTAT 監視 | SSOLDERRCNT を定期確認 | ORA-01555 の予兆を早期に把握する |
-- UNDO 関連パラメータを一括確認
SELECT NAME, VALUE, DESCRIPTION
FROM V$PARAMETER
WHERE NAME IN (
'undo_management', -- AUTO(推奨)または MANUAL
'undo_tablespace', -- 使用中の UNDO 表領域名
'undo_retention' -- 保持秒数
)
ORDER BY NAME;
-- 直近7日間で最も長かったクエリ実行時間(秒)
-- → UNDO_RETENTION の設定目安になる
SELECT MAX(MAXQUERYLEN) AS max_query_sec
FROM DBA_HIST_UNDOSTAT
WHERE BEGIN_TIME >= SYSDATE - 7;
まとめ
- 原因の特定:V$UNDOSTAT の SSOLDERRCNT で ORA-01555 の発生頻度・タイミングを確認する
- UNDO_RETENTION:最長クエリの実行時間より長く設定する。デフォルト 900 秒(15分)は多くの本番環境で不足する
- RETENTION GUARANTEE:UNDO 表領域が十分にあればオンにして確実に UNDO を保護する
- fetch-across-commit は禁止:カーソル FETCH 中に COMMIT するコードを書かない。BULK COLLECT で事前取得するパターンに置き換える
- AUTOEXTEND ON:UNDO 表領域を自動拡張可能にしておく
UNDO が絡む長時間クエリの挙動についてはトランザクション完全ガイドの読み取り一貫性・MVCC の仕組みも合わせて参照してください。UNDO 表領域を含む表領域の容量不足対応は容量不足の緊急対応完全ガイドも参考になります。また、誤操作からのデータ復旧には UNDO を利用するフラッシュバック完全ガイドも役立ちます。

