Oracleデータベースを運用していると、「表領域の空きが足りない」「特定のセグメントが異常に大きい」という問題に遭遇することがあります。その原因として非常に多いのがLOBセグメントの肥大化です。SYS_LOBで始まる見慣れないセグメントが表領域の大部分を占有しているケースは、実務で頻繁に発生します。
この記事では、LOBセグメントの仕組み・確認方法・削除・再編成・表領域の解放まで、実務で必要な手順を体系的に解説します。DELETE しても領域が返却されない理由や、ALTER TABLE MOVE LOB、SHRINK SPACE、PURGE RECYCLEBINなど、状況に応じた適切な対処法を網羅します。
この記事で分かること
- LOBセグメント(CLOB/BLOB/NCLOB)の仕組みと命名規則
- DBA_SEGMENTS / DBA_LOBS でLOBサイズを確認するSQL
- DELETE後に領域が解放されない理由と正しい解放手順
ALTER TABLE MOVE LOB によるLOBセグメントの再編成
SHRINK SPACE によるオンライン縮小
- DROP TABLE / PURGE RECYCLEBIN での完全削除
- LOBセグメントを別表領域に移動するベストプラクティス
- データファイルのRESIZEで表領域を縮小する方法
- LOB肥大化の原因調査・バッチ削除・定期メンテナンス
LOBセグメントとは
LOB(Large Object)は、大量のデータを格納するためのOracleのデータ型です。テキストデータにはCLOB、バイナリデータにはBLOB、Unicode テキストにはNCLOBを使用します。LOBカラムのデータは、通常の行データとは別のセグメントに格納されます。これがLOBセグメントです。
CLOB・BLOB・NCLOBの概要
| データ型 |
格納データ |
最大サイズ |
用途例 |
CLOB |
文字データ |
(4GB – 1) x DB_BLOCK_SIZE |
長文テキスト、XML、JSON |
BLOB |
バイナリデータ |
(4GB – 1) x DB_BLOCK_SIZE |
画像、PDF、ファイル |
NCLOB |
Unicode文字データ |
(4GB – 1) x DB_BLOCK_SIZE |
多言語テキスト |
LOBセグメントが表領域を圧迫する理由
LOBカラムを含むテーブルを作成すると、Oracle は自動的にLOBセグメントとLOBインデックスを作成します。通常の行データはテーブルセグメントに格納されますが、LOBデータはLOBセグメントに別途格納されます。
ポイント:LOBセグメントが肥大化する主な理由は次の3つです。(1) LOBカラムに大量のデータが蓄積される。(2) DELETE しても LOBセグメントの領域は自動的に解放されない。(3) UPDATE で LOBデータを更新すると、旧データの領域がそのまま残る(BASICFILE LOB の場合)。
このため、テーブルの行データは数MBなのに、LOBセグメントが数十GBに膨れ上がっているという状況が発生します。
SYS_LOB…OC…$$の命名規則
Oracle が自動生成するLOBセグメント名は、以下の命名規則に従います。
命名規則
-- LOBセグメント名の構造
SYS_LOB0000012345C00006$$
-- 各部分の意味
SYS_LOB : LOBセグメントであることを示すプレフィックス
0000012345 : テーブルのOBJECT_ID(DBA_OBJECTSで確認可能)
C : CLOBの場合はC、BLOBの場合もC
00006 : カラムの内部ID(INTCOL#)
$$ : サフィックス
LOBインデックスも同様に自動生成され、SYS_IL0000012345C00006$$のような名前になります。これらの名前からテーブルを特定するには、DBA_LOBSビューを使用します。
LOBセグメントの確認方法
LOBセグメントの問題を解決するには、まずどのLOBセグメントがどれくらいの領域を使っているかを正確に把握する必要があります。ここでは3つの確認方法を紹介します。
DBA_SEGMENTS / USER_SEGMENTSでLOBセグメントのサイズ確認
DBA_SEGMENTS(またはUSER_SEGMENTS)を使うと、LOBセグメントのサイズを直接確認できます。
SQL
-- LOBセグメントの一覧とサイズを確認
SELECT segment_name,
segment_type,
tablespace_name,
ROUND(bytes / 1024 / 1024, 2) AS size_mb
FROM dba_segments
WHERE segment_type = 'LOBSEGMENT'
ORDER BY bytes DESC;
実行結果
SEGMENT_NAME SEGMENT_TYPE TABLESPACE_NAME SIZE_MB
-------------------------- ------------ --------------- -------
SYS_LOB0000078523C00004$$ LOBSEGMENT USERS 8,542.00
SYS_LOB0000065210C00003$$ LOBSEGMENT USERS 3,128.50
SYS_LOB0000091045C00005$$ LOBSEGMENT USERS 1,256.75
DBA権限がない場合は、USER_SEGMENTSを使用してください。自スキーマのセグメントのみ表示されます。
DBA_LOBS / USER_LOBSでテーブルとの紐付き確認
SYS_LOB...というセグメント名だけでは、どのテーブルのどのカラムのLOBなのか判別できません。DBA_LOBSを使って紐付きを確認します。
SQL
-- LOBセグメントとテーブル・カラムの対応を確認
SELECT l.owner,
l.table_name,
l.column_name,
l.segment_name,
l.tablespace_name,
l.securefile
FROM dba_lobs l
WHERE l.owner = 'HR'
ORDER BY l.table_name, l.column_name;
実行結果
OWNER TABLE_NAME COLUMN_NAME SEGMENT_NAME TABLESPACE SECUREFILE
----- -------------- ----------- ---------------------------- ---------- ----------
HR DOCUMENTS FILE_DATA SYS_LOB0000078523C00004$$ USERS NO
HR DOCUMENTS THUMBNAIL SYS_LOB0000078523C00005$$ USERS NO
HR LOG_ENTRIES LOG_TEXT SYS_LOB0000065210C00003$$ USERS YES
SECUREFILE列がYESの場合は SecureFile LOB、NOの場合は BasicFile LOB です。この違いは後述するSHRINK SPACEの可否に関わります。
LOBセグメントが大きいテーブルTOP10を取得するSQL
実務で最もよく使うのが、LOBセグメントのサイズでソートして上位のテーブルを特定するSQLです。DBA_SEGMENTSとDBA_LOBSを結合して、テーブル名・カラム名・サイズを一覧表示します。
SQL
-- LOBセグメントが大きいテーブルTOP10
SELECT *
FROM (
SELECT l.owner,
l.table_name,
l.column_name,
l.segment_name,
l.securefile,
ROUND(s.bytes / 1024 / 1024, 2) AS lob_size_mb,
ROUND(s.bytes / 1024 / 1024 / 1024, 2) AS lob_size_gb
FROM dba_lobs l
JOIN dba_segments s
ON s.owner = l.owner
AND s.segment_name = l.segment_name
ORDER BY s.bytes DESC
)
WHERE ROWNUM <= 10;
実行結果
OWNER TABLE_NAME COLUMN_NAME SEGMENT_NAME SEC LOB_SIZE_MB LOB_SIZE_GB
----- --------------- ----------- ---------------------------- --- ----------- -----------
HR DOCUMENTS FILE_DATA SYS_LOB0000078523C00004$$ NO 8,542.00 8.34
HR AUDIT_LOG LOG_DETAIL SYS_LOB0000065210C00003$$ YES 3,128.50 3.06
APP FILE_STORAGE CONTENT SYS_LOB0000091045C00005$$ NO 1,256.75 1.23
HR DOCUMENTS THUMBNAIL SYS_LOB0000078523C00005$$ NO 845.25 0.83
APP EMAIL_ARCHIVE BODY_HTML SYS_LOB0000088712C00006$$ YES 512.00 0.50
ポイント:このSQLを定期的に実行し、LOBセグメントのサイズ推移を監視することで、肥大化の早期検知が可能になります。特にSECUREFILEがNO(BasicFile)のLOBは領域管理が非効率なため、優先的に監視対象にしてください。
LOBデータの削除と領域解放
LOBセグメントの肥大化を解消するには、単にDELETEするだけでは不十分です。ここでは、LOBデータを削除して実際に領域を解放するための方法を、段階的に解説します。
DELETEでのデータ削除(領域が解放されない問題)
通常のDELETE文やEMPTY_LOB関数でLOBデータを削除しても、LOBセグメントの領域はOSに返却されません。これは Oracle の LOB 領域管理の仕様によるものです。
SQL
-- LOBデータを含む行を削除
DELETE FROM documents
WHERE created_date < TO_DATE('2023-01-01', 'YYYY-MM-DD');
COMMIT;
-- LOBカラムを空にする(行は残る)
UPDATE documents
SET file_data = EMPTY_BLOB()
WHERE doc_id = 100;
COMMIT;
注意:上記のDELETEやEMPTY_BLOB()を実行しても、DBA_SEGMENTSで確認するLOBセグメントのサイズはほとんど変わりません。削除されたデータの領域は「再利用可能な空き領域」としてLOBセグメント内部に残りますが、表領域やOSには返却されません。領域を実際に解放するには、次に説明するMOVEまたはSHRINKが必要です。
ALTER TABLE … MOVE LOB(LOBセグメントの再編成)
ALTER TABLE MOVE LOBは、LOBセグメントを再作成して使用中のデータだけを新しいセグメントにコピーします。これにより、未使用領域が除去され、LOBセグメントのサイズが大幅に縮小されます。
SQL
-- LOBセグメントを再編成(同じ表領域内で再作成)
ALTER TABLE documents
MOVE LOB (file_data) STORE AS (
TABLESPACE users
);
複数のLOBカラムがある場合は、それぞれ個別に指定するか、テーブル全体をMOVEします。
SQL
-- 複数LOBカラムをまとめて再編成
ALTER TABLE documents
MOVE
LOB (file_data) STORE AS (TABLESPACE users)
LOB (thumbnail) STORE AS (TABLESPACE users);
注意:ALTER TABLE MOVEを実行すると、そのテーブルのすべてのインデックスが UNUSABLE(無効)状態になります。MOVE完了後に必ずインデックスを再構築してください(後述)。また、MOVE中はテーブルへのDML(INSERT/UPDATE/DELETE)がブロックされます。
SHRINK SPACE(オンライン縮小)
SHRINK SPACEは、テーブルやLOBセグメントの未使用領域をオンラインで縮小するコマンドです。MOVE と異なり、インデックスが無効化されないのが大きなメリットです。
SQL
-- 前提: 行移動を有効にする(未設定の場合)
ALTER TABLE documents ENABLE ROW MOVEMENT;
-- LOBセグメントを縮小
ALTER TABLE documents
MODIFY LOB (file_data) (
SHRINK SPACE
);
SHRINK SPACE CASCADEを使うと、テーブル本体とLOBセグメントの両方を一度に縮小できます。
SQL
-- テーブル + LOBセグメントをまとめて縮小
ALTER TABLE documents SHRINK SPACE CASCADE;
| 比較項目 |
MOVE LOB |
SHRINK SPACE |
| オンライン実行 |
不可(DMLブロック) |
可能 |
| インデックスへの影響 |
UNUSABLE になる |
影響なし |
| BasicFile LOB対応 |
対応 |
非対応 |
| SecureFile LOB対応 |
対応 |
対応 |
| ROW MOVEMENT必要 |
不要 |
必要 |
| 領域解放の効率 |
非常に高い |
中程度 |
ALTER TABLE MOVE + インデックス再構築
ALTER TABLE MOVEを実行した後は、必ずインデックスを再構築する必要があります。以下は、無効化されたインデックスを確認して再構築する手順です。
SQL
-- Step 1: MOVE LOB を実行
ALTER TABLE documents
MOVE LOB (file_data) STORE AS (
TABLESPACE users
);
-- Step 2: 無効化されたインデックスを確認
SELECT index_name,
index_type,
status
FROM user_indexes
WHERE table_name = 'DOCUMENTS'
AND status = 'UNUSABLE';
-- Step 3: インデックスを再構築
ALTER INDEX pk_documents REBUILD;
ALTER INDEX idx_documents_date REBUILD;
対象テーブルのインデックスが多い場合は、PL/SQLで一括再構築すると効率的です。
PL/SQL
-- 無効インデックスを一括再構築
BEGIN
FOR rec IN (
SELECT index_name
FROM user_indexes
WHERE table_name = 'DOCUMENTS'
AND status = 'UNUSABLE'
) LOOP
EXECUTE IMMEDIATE
'ALTER INDEX ' || rec.index_name || ' REBUILD';
DBMS_OUTPUT.PUT_LINE('Rebuilt: ' || rec.index_name);
END LOOP;
END;
/
テーブルごとDROPしてLOBセグメントを解放
テーブル自体が不要であれば、DROP TABLEでテーブルとLOBセグメントを一括で削除するのが最も確実な方法です。ただし、リサイクルビンの動作に注意が必要です。
DROP TABLE … PURGE
DROP TABLEを実行すると、テーブル本体・LOBセグメント・LOBインデックス・通常のインデックスがすべて削除されます。PURGEを付けるとリサイクルビンを経由せず、即座に領域が解放されます。
SQL
-- テーブルをリサイクルビンに入れずに即削除
DROP TABLE documents PURGE;
-- 外部キー参照がある場合は CASCADE CONSTRAINTS を付ける
DROP TABLE documents CASCADE CONSTRAINTS PURGE;
ポイント:PURGEを付けた場合、FLASHBACK TABLE ... TO BEFORE DROPで復元することはできません。重要なデータがある場合は、DROP前に必ずバックアップを取得してください。
リサイクルビンのLOBセグメント(BIN$…)
PURGEを付けずにDROP TABLEした場合、テーブルはリサイクルビンに移動します。このとき、LOBセグメントもBIN$...という名前でリサイクルビン内に残り、表領域は解放されません。
SQL
-- リサイクルビンの内容を確認
SELECT object_name,
original_name,
type,
ts_name,
ROUND(space * 8192 / 1024 / 1024, 2) AS size_mb
FROM dba_recyclebin
WHERE type IN ('TABLE', 'LOB', 'LOB INDEX')
ORDER BY droptime DESC;
実行結果
OBJECT_NAME ORIGINAL_NAME TYPE TS_NAME SIZE_MB
----------------------------- ----------------------------- --------- ------- -------
BIN$abc123== DOCUMENTS TABLE USERS 12.50
BIN$def456== SYS_LOB0000078523C00004$$ LOB USERS 8,542.00
BIN$ghi789== SYS_IL0000078523C00004$$ LOB INDEX USERS 8.25
リサイクルビンのLOBセグメントが表領域の大部分を占有しているケースは非常に多いです。特に大きなLOBを持つテーブルを何度もDROP→再作成している環境では、この確認が重要です。
PURGE RECYCLEBINでの完全削除
SQL
-- 自スキーマのリサイクルビンを空にする
PURGE RECYCLEBIN;
-- 特定テーブルだけリサイクルビンから完全削除
PURGE TABLE documents;
-- DBA権限: 全ユーザーのリサイクルビンを空にする
PURGE DBA_RECYCLEBIN;
-- 特定表領域のリサイクルビンだけ空にする
PURGE TABLESPACE users;
注意:PURGE RECYCLEBINを実行すると、リサイクルビン内のすべてのオブジェクトが完全削除されます。FLASHBACK TABLEでの復元ができなくなるため、必要なテーブルがリサイクルビンに含まれていないか確認してから実行してください。
LOBセグメントの表領域を変更する
LOBセグメントが特定の表領域に集中して圧迫している場合、LOBセグメントだけを別の表領域に移動することで問題を解決できます。
ALTER TABLE … MOVE LOB (…) STORE AS (TABLESPACE 新表領域)
MOVE LOBで移動先の表領域を指定すると、LOBセグメントを別の表領域に移動できます。テーブル本体はそのままで、LOBだけを移動できるのがポイントです。
SQL
-- LOBセグメントを LOB_DATA 表領域に移動
ALTER TABLE documents
MOVE LOB (file_data) STORE AS (
TABLESPACE lob_data
);
-- 移動後の確認
SELECT table_name,
column_name,
segment_name,
tablespace_name
FROM user_lobs
WHERE table_name = 'DOCUMENTS';
実行結果
TABLE_NAME COLUMN_NAME SEGMENT_NAME TABLESPACE_NAME
---------- ----------- ---------------------------- ---------------
DOCUMENTS FILE_DATA SYS_LOB0000078530C00004$$ LOB_DATA
DOCUMENTS THUMBNAIL SYS_LOB0000078523C00005$$ USERS
LOBだけ別表領域に移動するベストプラクティス
大量のLOBデータを扱うシステムでは、LOB専用の表領域を作成してLOBセグメントを分離するのがベストプラクティスです。
SQL
-- Step 1: LOB専用の表領域を作成
CREATE TABLESPACE lob_data
DATAFILE '/u01/oradata/mydb/lob_data01.dbf'
SIZE 10G
AUTOEXTEND ON NEXT 1G
MAXSIZE 50G;
-- Step 2: 既存テーブルのLOBを移動
ALTER TABLE documents
MOVE LOB (file_data) STORE AS (TABLESPACE lob_data);
-- Step 3: インデックスを再構築
ALTER INDEX pk_documents REBUILD;
-- Step 4: 新規テーブル作成時はLOBの格納先を指定
CREATE TABLE new_documents (
doc_id NUMBER PRIMARY KEY,
doc_name VARCHAR2(200),
file_data BLOB
)
TABLESPACE users
LOB (file_data) STORE AS SECUREFILE (
TABLESPACE lob_data
DISABLE STORAGE IN ROW
);
| 設計方針 |
メリット |
| LOB専用表領域を分離 |
テーブルデータとLOBデータのI/Oを分散できる |
| AUTOEXTEND ON を設定 |
LOB肥大化による表領域フルを防止できる |
| SECUREFILE を使用 |
圧縮・重複排除・SHRINK SPACEが使える |
| DISABLE STORAGE IN ROW |
すべてのLOBデータがLOBセグメントに格納される |
表領域の空き容量を確認・解放する
LOBセグメントを削除・縮小した後、表領域に空き領域が生まれます。しかし、表領域の空きがあるだけではデータファイルのサイズは縮小されません。ここでは、空き容量の確認からデータファイルの縮小までの手順を解説します。
DBA_FREE_SPACEで空き確認
SQL
-- 表領域ごとの使用量・空き容量を確認
SELECT t.tablespace_name,
ROUND(t.total_mb, 2) AS total_mb,
ROUND(f.free_mb, 2) AS free_mb,
ROUND(t.total_mb - f.free_mb, 2) AS used_mb,
ROUND(f.free_mb / t.total_mb * 100, 1) AS free_pct
FROM (
SELECT tablespace_name,
SUM(bytes) / 1024 / 1024 AS total_mb
FROM dba_data_files
GROUP BY tablespace_name
) t
LEFT JOIN (
SELECT tablespace_name,
SUM(bytes) / 1024 / 1024 AS free_mb
FROM dba_free_space
GROUP BY tablespace_name
) f
ON t.tablespace_name = f.tablespace_name
ORDER BY free_pct;
実行結果
TABLESPACE_NAME TOTAL_MB FREE_MB USED_MB FREE_PCT
--------------- --------- -------- -------- --------
SYSTEM 800.00 45.00 755.00 5.6
USERS 20,480.00 12,800.00 7,680.00 62.5
LOB_DATA 10,240.00 9,500.00 740.00 92.8
ALTER DATABASE DATAFILE … RESIZEで縮小
表領域に大量の空きがあっても、データファイルのサイズは自動では縮小されません。ALTER DATABASE DATAFILE ... RESIZEでデータファイルを手動で縮小できます。
SQL
-- データファイルの現在のサイズと縮小可能サイズを確認
SELECT file_name,
ROUND(bytes / 1024 / 1024, 2) AS current_mb,
autoextensible
FROM dba_data_files
WHERE tablespace_name = 'USERS';
-- データファイルを縮小
ALTER DATABASE DATAFILE
'/u01/oradata/mydb/users01.dbf'
RESIZE 8G;
注意:データファイルは、最後に使用されているブロックよりも小さくRESIZEすることはできません。空き領域がファイルの末尾にない場合、ORA-03297: file contains used data beyond requested RESIZE valueエラーが発生します。この場合は、先にMOVEで末尾のセグメントを移動してからRESIZEしてください。
データファイル自動拡張の確認
LOBセグメントの肥大化を防ぐには、データファイルの自動拡張設定を適切に管理することが重要です。
SQL
-- 全データファイルの自動拡張設定を確認
SELECT tablespace_name,
file_name,
ROUND(bytes / 1024 / 1024, 2) AS current_mb,
autoextensible,
ROUND(maxbytes / 1024 / 1024, 2) AS max_mb,
ROUND(increment_by * 8192 / 1024 / 1024, 2) AS increment_mb
FROM dba_data_files
ORDER BY tablespace_name, file_name;
ポイント:自動拡張が有効(AUTOEXTENSIBLE = YES)でMAXSIZE UNLIMITEDに設定されている場合、LOBが際限なく拡張してディスクを消費する可能性があります。LOB専用表領域にはMAXSIZEの上限を必ず設定してください。
実務でよく使うシナリオ
ここでは、LOBセグメントの問題に直面したときの実務的な対処手順を、よくあるシナリオ別に紹介します。
LOB肥大化の原因調査手順
「表領域が逼迫している」というアラートが発生したとき、LOBセグメントが原因かどうかを特定する手順です。
SQL
-- Step 1: 表領域の使用率を確認
SELECT tablespace_name,
ROUND(used_space * 8192 / 1024 / 1024, 2) AS used_mb,
ROUND(tablespace_size * 8192 / 1024 / 1024, 2) AS total_mb,
ROUND(used_percent, 1) AS used_pct
FROM dba_tablespace_usage_metrics
ORDER BY used_pct DESC;
-- Step 2: 該当表領域のセグメントサイズTOP10
SELECT segment_name,
segment_type,
owner,
ROUND(bytes / 1024 / 1024, 2) AS size_mb
FROM dba_segments
WHERE tablespace_name = 'USERS'
ORDER BY bytes DESC
FETCH FIRST 10 ROWS ONLY;
-- Step 3: LOBセグメントならテーブルを特定
SELECT l.table_name,
l.column_name,
l.segment_name,
ROUND(s.bytes / 1024 / 1024, 2) AS lob_mb,
(SELECT COUNT(*) FROM all_tables t
WHERE t.owner = l.owner
AND t.table_name = l.table_name) AS row_count_approx
FROM dba_lobs l
JOIN dba_segments s
ON s.segment_name = l.segment_name
AND s.owner = l.owner
WHERE l.segment_name = 'SYS_LOB0000078523C00004$$';
大量LOBデータの安全な削除(バッチ処理)
LOBデータが大量にあるテーブルでは、一度にDELETEするとUNDO表領域を消費しすぎてエラーになります。バッチ処理で少しずつ削除するのが安全です。
PL/SQL
-- 1000件ずつバッチで削除
DECLARE
v_rows NUMBER;
v_total NUMBER := 0;
BEGIN
LOOP
DELETE FROM documents
WHERE created_date < TO_DATE('2023-01-01', 'YYYY-MM-DD')
AND ROWNUM <= 1000;
v_rows := SQL%ROWCOUNT;
v_total := v_total + v_rows;
COMMIT;
DBMS_OUTPUT.PUT_LINE(
'Deleted: ' || v_rows ||
' rows (Total: ' || v_total || ')'
);
EXIT WHEN v_rows = 0;
-- UNDO表領域への負荷を軽減
DBMS_LOCK.SLEEP(1);
END LOOP;
DBMS_OUTPUT.PUT_LINE(
'Complete. Total deleted: ' || v_total || ' rows'
);
END;
/
ポイント:バッチ削除完了後、LOBセグメントの領域はまだ解放されていません。削除後にALTER TABLE MOVE LOBまたはSHRINK SPACEを実行して、領域を実際に解放してください。
LOBセグメントの定期メンテナンス
LOBセグメントの肥大化を防ぐには、定期的なメンテナンスが重要です。以下のSQLをメンテナンスジョブに組み込むことで、LOBの肥大化を早期に検知できます。
SQL
-- LOBセグメントのサイズ推移を記録するテーブル
CREATE TABLE lob_size_history (
check_date DATE DEFAULT SYSDATE,
owner VARCHAR2(128),
table_name VARCHAR2(128),
column_name VARCHAR2(128),
lob_size_mb NUMBER
);
-- 日次で実行するサイズ記録SQL
INSERT INTO lob_size_history
(owner, table_name, column_name, lob_size_mb)
SELECT l.owner,
l.table_name,
l.column_name,
ROUND(s.bytes / 1024 / 1024, 2)
FROM dba_lobs l
JOIN dba_segments s
ON s.owner = l.owner
AND s.segment_name = l.segment_name
WHERE s.bytes > 100 * 1024 * 1024; -- 100MB以上のみ記録
COMMIT;
よくあるミスと注意点
LOBセグメントの管理では、知らないと痛い目を見る落とし穴がいくつかあります。ここでは、実務で特に注意すべきポイントをまとめます。
DELETEしてもLOBの領域は返却されない
これはLOBセグメント管理で最もよくある誤解です。通常のテーブルでは、DELETEした行の領域はHighWaterMark以下の空き領域として再利用されますが、LOBセグメントでは事情が異なります。
| 操作 |
テーブルセグメント |
LOBセグメント |
| DELETE |
HWM以下で再利用可能 |
セグメント内で再利用可能(縮小はしない) |
| TRUNCATE |
領域が解放される |
領域が解放される |
| MOVE |
再編成(HWMリセット) |
再編成(未使用領域除去) |
| SHRINK SPACE |
オンライン縮小 |
SecureFileのみ対応 |
MOVE後にインデックスが無効化される
ALTER TABLE MOVE(LOB MOVEを含む)を実行すると、テーブルのROWIDが変更されます。ROWIDに依存するインデックスはすべてUNUSABLE状態になり、そのまま放置するとSELECTやDMLでエラーが発生します。
エラー例
ORA-01502: index 'HR.PK_DOCUMENTS' or partition of such index is in unusable state
MOVE後は必ず以下を実行してください。
SQL
-- 無効インデックスの一覧を確認
SELECT index_name, status
FROM user_indexes
WHERE status = 'UNUSABLE';
-- 各インデックスをREBUILD
ALTER INDEX pk_documents REBUILD;
ALTER INDEX idx_documents_date REBUILD;
SHRINK SPACEが使えない条件(BASICFILE LOB)
SHRINK SPACEは便利なコマンドですが、BasicFile LOBでは使用できません。実行すると以下のエラーが発生します。
エラー例
ORA-10635: Invalid segment or tablespace type
LOBがBasicFileかSecureFileかを確認し、BasicFileの場合はMOVE LOBで対処してください。
SQL
-- LOBの種類を確認
SELECT table_name,
column_name,
securefile
FROM user_lobs;
実行結果
TABLE_NAME COLUMN_NAME SECUREFILE
-------------- ----------- ----------
DOCUMENTS FILE_DATA NO <-- BasicFile(SHRINK不可)
LOG_ENTRIES LOG_TEXT YES <-- SecureFile(SHRINK可能)
SecureFileとBasicFileの違い
Oracle 11g以降ではSecureFile LOBが導入され、多くの点でBasicFile LOBより優れています。新規テーブルを作成する際は、SecureFileを使うことを推奨します。
| 機能 |
BasicFile LOB |
SecureFile LOB |
| 圧縮 |
非対応 |
対応(LOW/MEDIUM/HIGH) |
| 重複排除 |
非対応 |
対応(DEDUPLICATE) |
| 暗号化 |
非対応 |
対応(ENCRYPT) |
| SHRINK SPACE |
非対応 |
対応 |
| パフォーマンス |
従来型 |
大幅に改善 |
| 前提条件 |
なし |
ASSM表領域が必要 |
SQL
-- BasicFile LOB を SecureFile LOB に変換する
ALTER TABLE documents
MOVE LOB (file_data) STORE AS SECUREFILE (
TABLESPACE lob_data
);
-- 変換後にインデックスを再構築
ALTER INDEX pk_documents REBUILD;
まとめ(手順フロー表)
LOBセグメントの削除と表領域の解放について解説しました。最後に、状況に応じた対処法の早見表を掲載します。
| 状況 |
対処法 |
注意点 |
| LOBのサイズを確認したい |
DBA_SEGMENTS + DBA_LOBS を結合 |
DBA権限がなければ USER_ 系を使用 |
| テーブルは残してLOBを縮小したい |
DELETE + MOVE LOB |
MOVE後にインデックスREBUILD必須 |
| オンラインでLOBを縮小したい |
SHRINK SPACE |
SecureFile LOBのみ対応 |
| テーブルごと不要 |
DROP TABLE ... PURGE |
PURGEを忘れるとリサイクルビンに残る |
| リサイクルビンが表領域を圧迫 |
PURGE RECYCLEBIN |
FLASHBACK復元不可になる |
| LOBを別表領域に移動したい |
MOVE LOB (...) STORE AS (TABLESPACE ...) |
移動後にインデックスREBUILD必須 |
| データファイルを縮小したい |
ALTER DATABASE DATAFILE ... RESIZE |
末尾に使用中ブロックがあるとエラー |
| BasicFileをSecureFileにしたい |
MOVE LOB (...) STORE AS SECUREFILE |
ASSM表領域が必要 |
LOBセグメントの管理は、以下の手順フローで対処するのが効率的です。
- 現状確認: DBA_SEGMENTS + DBA_LOBS でLOBサイズとテーブルの紐付きを確認
- 不要データの削除: バッチ処理で安全にDELETE(大量データの場合)
- 領域の解放: SecureFileなら SHRINK SPACE、BasicFileなら MOVE LOB で再編成
- インデックス再構築: MOVEを使った場合は必ず REBUILD を実行
- 表領域の縮小: DBA_FREE_SPACE で空きを確認し、DATAFILE RESIZE で縮小
- 予防策: LOB専用表領域の分離、SecureFileの採用、サイズ監視の定期実行
特に「DELETEしても領域が返却されない」という点は最大の落とし穴です。LOBセグメントの領域を実際に解放するには、MOVE LOBまたはSHRINK SPACEが必須であることを覚えておいてください。BasicFile LOBを使っている場合は、この機会にSecureFileへの変換も検討しましょう。