Oracle データベースを長期間運用すると、大量の DELETE や UPDATE によってテーブル内に無駄な空き領域が蓄積し、表領域内の空きエクステントが分散します。これが断片化であり、フルスキャンの性能低下やディスクの無駄遣いの原因になります。
本記事では、2 種類の断片化(テーブル断片化 / 表領域断片化)の違い、断片化の診断 SQL、ALTER TABLE MOVE / SHRINK SPACE / COALESCEによる再編成手順まで解説します。
・テーブル断片化(HWM 問題)と表領域断片化の違い
・断片化を診断する SQL(HWM / 実データ量の乖離検出)
・ALTER TABLE MOVE による再編成
・SHRINK SPACE によるオンライン圧縮
・ALTER TABLESPACE COALESCE による空きエクステント結合
・インデックスの再構築(REBUILD)
・12c MOVE ONLINE(ダウンタイムなし再編成)
・LOB セグメントの断片化対策
2 種類の断片化
| 種類 | 発生場所 | 原因 | 影響 | 解消方法 |
|---|---|---|---|---|
| テーブル断片化 (HWM 問題) |
テーブル内部 | DELETE で行が消えても HWM(High Water Mark)は下がらない | フルテーブルスキャンが遅い(空ブロックも読む) | ALTER TABLE MOVE / SHRINK SPACE |
| 表領域断片化 (空きエクステント分散) |
表領域全体 | テーブルの作成・削除が繰り返され空きが分散 | 大きなエクステントを確保できず ORA-01653 | COALESCE / テーブル MOVE |
テーブルが「これまでに使用した最大ブロック位置」のマークです。DELETE で行を削除しても HWM は下がりません。フルテーブルスキャンは HWM までの全ブロックを読むため、大量 DELETE 後のテーブルはデータ量が少ないのにスキャンが遅いという症状が出ます。
断片化の診断 SQL
テーブル断片化(HWM と実データ量の乖離)
-- セグメントサイズ(HWM まで)と実際の行数から無駄を推定
SELECT t.table_name,
ROUND(s.bytes/1024/1024, 1) AS segment_mb,
t.num_rows,
t.avg_row_len,
ROUND(t.num_rows * t.avg_row_len / 1024 / 1024, 1) AS est_data_mb,
ROUND(s.bytes/1024/1024 - t.num_rows * t.avg_row_len / 1024 / 1024, 1) AS wasted_mb
FROM user_tables t
JOIN user_segments s ON t.table_name = s.segment_name AND s.segment_type = 'TABLE'
WHERE t.num_rows > 0
AND s.bytes > t.num_rows * t.avg_row_len * 3 -- セグメントが推定の 3 倍以上
ORDER BY wasted_mb DESC;
-- ※ num_rows / avg_row_len は統計情報ベース
-- 正確にするには DBMS_STATS.GATHER_TABLE_STATS を先に実行
-- テーブルの空きブロック数を正確に取得
DECLARE
v_unformatted_blocks NUMBER;
v_unformatted_bytes NUMBER;
v_fs1_blocks NUMBER; v_fs1_bytes NUMBER;
v_fs2_blocks NUMBER; v_fs2_bytes NUMBER;
v_fs3_blocks NUMBER; v_fs3_bytes NUMBER;
v_fs4_blocks NUMBER; v_fs4_bytes NUMBER;
v_full_blocks NUMBER; v_full_bytes NUMBER;
BEGIN
DBMS_SPACE.SPACE_USAGE(
segment_owner => USER,
segment_name => 'EMPLOYEES',
segment_type => 'TABLE',
unformatted_blocks => v_unformatted_blocks,
unformatted_bytes => v_unformatted_bytes,
fs1_blocks => v_fs1_blocks, fs1_bytes => v_fs1_bytes,
fs2_blocks => v_fs2_blocks, fs2_bytes => v_fs2_bytes,
fs3_blocks => v_fs3_blocks, fs3_bytes => v_fs3_bytes,
fs4_blocks => v_fs4_blocks, fs4_bytes => v_fs4_bytes,
full_blocks => v_full_blocks, full_bytes => v_full_bytes
);
DBMS_OUTPUT.PUT_LINE('Full blocks: ' || v_full_blocks);
DBMS_OUTPUT.PUT_LINE('0-25% free blocks: ' || v_fs1_blocks);
DBMS_OUTPUT.PUT_LINE('25-50% free blocks: ' || v_fs2_blocks);
DBMS_OUTPUT.PUT_LINE('50-75% free blocks: ' || v_fs3_blocks);
DBMS_OUTPUT.PUT_LINE('75-100% free blocks:' || v_fs4_blocks);
DBMS_OUTPUT.PUT_LINE('Unformatted: ' || v_unformatted_blocks);
END;
/
-- fs3/fs4/unformatted が多い = 断片化が激しい
表領域断片化(空きエクステントの分散)
-- 空きエクステント数と最大連続空き
SELECT tablespace_name,
COUNT(*) AS free_extents,
ROUND(SUM(bytes)/1024/1024, 1) AS total_free_mb,
ROUND(MAX(bytes)/1024/1024, 1) AS max_contiguous_mb
FROM dba_free_space
GROUP BY tablespace_name
HAVING COUNT(*) >= 20 -- 20 個以上の断片 = 要注意
ORDER BY free_extents DESC;
DBA_FREE_SPACE の詳細は「DBA_FREE_SPACE で空き容量を調べる方法」を参照してください。
ALTER TABLE MOVE(テーブルの再編成)
テーブルのデータを新しいブロックに再配置し、HWM をリセットします。最も効果的な断片化解消方法です。
-- 同じ表領域内で再配置 ALTER TABLE hr.employees MOVE; -- 別の表領域に移動しながら再編成 ALTER TABLE hr.employees MOVE TABLESPACE new_data; -- MOVE 後は全インデックスが UNUSABLE → 再構築が必須 ALTER INDEX hr.idx_emp_dept REBUILD; ALTER INDEX hr.pk_employees REBUILD;
-- MOVE ONLINE: DML をブロックせずに再編成(12c 以降) ALTER TABLE hr.employees MOVE ONLINE; -- MOVE ONLINE ではインデックスも自動的にメンテナンスされる -- → REBUILD 不要(12c 以降の大きなメリット)
| 方式 | DML 中の実行 | インデックス | バージョン |
|---|---|---|---|
| ALTER TABLE MOVE | 不可(テーブルロック) | UNUSABLE(REBUILD 必要) | 全バージョン |
| ALTER TABLE MOVE ONLINE | 可能(DML 継続) | 自動メンテナンス(REBUILD 不要) | 12c 以降 |
ALTER TABLE MOVE で ROWID が変わるため、テーブル上の全インデックスが UNUSABLE になります。MOVE 後に
ALTER INDEX ... REBUILD を必ず実行してください。12c の MOVE ONLINE ではインデックスも自動的に更新されるため REBUILD は不要です。-- テーブルの全インデックスを一括再構築
BEGIN
FOR rec IN (
SELECT index_name FROM user_indexes
WHERE table_name = 'EMPLOYEES'
) LOOP
EXECUTE IMMEDIATE 'ALTER INDEX ' || rec.index_name || ' REBUILD';
END LOOP;
END;
/
SHRINK SPACE(オンライン圧縮)
SHRINK SPACE はテーブルの行を前方のブロックに移動し、HWM を下げて末尾の空き領域を解放します。MOVE と異なりオンライン(DML 継続中)で実行できます。
-- (1) 行移動を有効化(初回のみ) ALTER TABLE hr.employees ENABLE ROW MOVEMENT; -- (2) SHRINK SPACE 実行 ALTER TABLE hr.employees SHRINK SPACE; -- CASCADE: テーブル + 全インデックスを同時に圧縮 ALTER TABLE hr.employees SHRINK SPACE CASCADE; -- COMPACT: 行の移動だけ実行(HWM は後で下げる = 2 段階) ALTER TABLE hr.employees SHRINK SPACE COMPACT; -- 後日: HWM を下げる ALTER TABLE hr.employees SHRINK SPACE;
| オプション | 動作 | ロック |
|---|---|---|
| SHRINK SPACE | 行を前方に移動 + HWM を下げる | 短時間のロック(HWM 変更時のみ) |
| SHRINK SPACE CASCADE | テーブル + 全インデックスを同時に圧縮 | 短時間のロック |
| SHRINK SPACE COMPACT | 行の移動だけ(HWM は後で下げる) | ロックなし |
・MOVE: 効果が大きい(完全に再配置)。11g ではロックが発生。12c MOVE ONLINE ならロックなし
・SHRINK SPACE: オンラインで実行可能だが効果は MOVE より小さい場合がある
・12c 以降なら MOVE ONLINE が最も推奨(効果大 + ロックなし + REBUILD 不要)
ALTER TABLESPACE COALESCE
-- 隣接する空きエクステントを結合
ALTER TABLESPACE users COALESCE;
-- COALESCE 前後の比較
SELECT tablespace_name, COUNT(*) AS free_extents,
ROUND(MAX(bytes)/1024/1024, 1) AS max_contiguous_mb
FROM dba_free_space
WHERE tablespace_name = 'USERS'
GROUP BY tablespace_name;
テーブルデータに挟まれた空きは結合できません。根本的に断片化を解消するにはテーブルの MOVE で再配置する必要があります。COALESCE は「ORA-01653 の応急処置」として使い、恒久対策は MOVE で行うのが定石です。
インデックスの断片化と再構築
-- ANALYZE INDEX で断片化を確認
ANALYZE INDEX hr.idx_emp_dept VALIDATE STRUCTURE;
-- INDEX_STATS で削除率を確認
SELECT name, height, lf_rows, del_lf_rows,
ROUND(del_lf_rows / NULLIF(lf_rows, 0) * 100, 1) AS del_pct
FROM index_stats;
-- del_pct が 20% 以上なら REBUILD を検討
-- 基本 ALTER INDEX hr.idx_emp_dept REBUILD; -- ONLINE: DML をブロックせずに再構築 ALTER INDEX hr.idx_emp_dept REBUILD ONLINE; -- NOLOGGING + PARALLEL で高速再構築(メンテナンス時間帯) ALTER INDEX hr.idx_emp_dept REBUILD NOLOGGING PARALLEL 4; ALTER INDEX hr.idx_emp_dept LOGGING; ALTER INDEX hr.idx_emp_dept NOPARALLEL;
インデックスの詳細は「インデックスの作成・再構築・削除完全ガイド」を参照してください。
LOB セグメントの断片化対策
-- LOB セグメントのサイズ確認
SELECT l.table_name, l.column_name, l.segment_name,
ROUND(s.bytes/1024/1024, 1) AS lob_mb
FROM user_lobs l
JOIN user_segments s ON l.segment_name = s.segment_name
ORDER BY s.bytes DESC;
-- LOB の再編成: ALTER TABLE MOVE で LOB も一緒に移動
ALTER TABLE hr.documents MOVE
LOB (content) STORE AS (TABLESPACE lob_data);
-- 12c 以降: MOVE ONLINE
ALTER TABLE hr.documents MOVE ONLINE
LOB (content) STORE AS (TABLESPACE lob_data);
SHRINK SPACE は通常のテーブルデータには有効ですが、SecureFiles LOB 以外の LOB セグメント(BasicFiles LOB)には効果がありません。LOB の断片化を解消するには ALTER TABLE MOVE を使ってください。
断片化解消の完全な手順
| # | 手順 | SQL |
|---|---|---|
| 1 | 統計情報を更新 | EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, ‘TABLE_NAME’) |
| 2 | 断片化の診断(無駄領域の確認) | 前述の診断 SQL で segment_mb vs est_data_mb を比較 |
| 3 | 再編成の実行 | ALTER TABLE t MOVE ONLINE(12c+)/ SHRINK SPACE CASCADE |
| 4 | インデックスの再構築(MOVE の場合) | ALTER INDEX idx REBUILD(MOVE ONLINE なら不要) |
| 5 | 統計情報を再収集 | EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, ‘TABLE_NAME’) |
| 6 | 効果の確認 | セグメントサイズの削減を確認 |
-- 再編成前後のセグメントサイズを比較
SELECT segment_name, segment_type,
ROUND(bytes/1024/1024, 1) AS size_mb
FROM user_segments
WHERE segment_name = 'EMPLOYEES';
-- 再編成前: 500 MB → 再編成後: 150 MB = 350 MB 削減
実務パターン集
パターン(1): 大量 DELETE 後のテーブルを圧縮(12c+)
-- 大量 DELETE でデータが 1/10 になったテーブルを再編成
ALTER TABLE hr.audit_log MOVE ONLINE;
-- インデックスも自動メンテナンス(12c MOVE ONLINE)
-- 統計再収集
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'AUDIT_LOG');
パターン(2): 全テーブルを一括再編成(11g)
-- 断片化が激しいテーブルを一括 MOVE + REBUILD
BEGIN
FOR rec IN (
SELECT t.table_name
FROM user_tables t
JOIN user_segments s ON t.table_name = s.segment_name
WHERE s.segment_type = 'TABLE'
AND t.num_rows > 0
AND s.bytes > t.num_rows * t.avg_row_len * 3
) LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' || rec.table_name || ' MOVE';
FOR idx IN (
SELECT index_name FROM user_indexes
WHERE table_name = rec.table_name
) LOOP
EXECUTE IMMEDIATE 'ALTER INDEX ' || idx.index_name || ' REBUILD';
END LOOP;
END LOOP;
END;
/
パターン(3): SHRINK SPACE をオンラインで実行
-- 業務時間中にオンラインで圧縮 ALTER TABLE hr.orders ENABLE ROW MOVEMENT; ALTER TABLE hr.orders SHRINK SPACE CASCADE;
パターン(4): 表領域の断片化を COALESCE で応急処置
-- ORA-01653 が出た → COALESCE で応急処置 ALTER TABLESPACE users COALESCE; -- 効果確認 SELECT COUNT(*) AS free_extents, ROUND(MAX(bytes)/1024/1024, 1) AS max_mb FROM dba_free_space WHERE tablespace_name = 'USERS';
よくある質問
ALTER TABLE MOVE ONLINE はEnterprise Edition 専用です。Standard Edition では通常の MOVE(テーブルロック発生)を使うか、SHRINK SPACE で対処してください。TRUNCATE TABLE は HWM をリセットし、全ブロックを解放するため断片化は完全に解消されます。ただしデータも全件消えるため、データを保持したまま断片化を解消するには MOVE / SHRINK SPACE を使います。まとめ
断片化の解消方法をまとめます。
| やりたいこと | 方法 |
|---|---|
| テーブルの断片化を診断 | user_tables.num_rows * avg_row_len vs user_segments.bytes を比較 |
| テーブルを再編成(11g) | ALTER TABLE t MOVE + ALTER INDEX idx REBUILD |
| テーブルを再編成(12c+ 推奨) | ALTER TABLE t MOVE ONLINE(ロックなし + REBUILD 不要) |
| オンラインで圧縮 | ENABLE ROW MOVEMENT + SHRINK SPACE CASCADE |
| 表領域の空きエクステントを結合 | ALTER TABLESPACE ts COALESCE |
| インデックスの断片化を解消 | ALTER INDEX idx REBUILD [ONLINE] |
| LOB の断片化を解消 | ALTER TABLE t MOVE LOB (col) STORE AS (…) |
空きエクステントの分析は「DBA_FREE_SPACE で空き容量を調べる方法」、インデックスの詳細は「インデックスの作成・再構築・削除完全ガイド」、表領域全般は「表領域(Tablespace)完全ガイド」も併せて参照してください。

