【Oracle】容量が大きいテーブルを探す方法|DBA_SEGMENTS・HWM・圧縮・パーティション別サイズ確認SQL完全ガイド

【Oracle】容量が大きいテーブルを探す方法|DBA_SEGMENTS・HWM・圧縮・パーティション別サイズ確認SQL完全ガイド Oracle

「Oracle のディスクがいっぱいに近い」「どのテーブルが容量を食っているか分からない」そんなとき、まず実行すべき SQL を体系的にまとめました。DBA_SEGMENTS を使ったテーブルサイズ上位の特定から、パーティション別集計・LOB セグメントの把握・HWM(高水位標)の確認まで、実務で即使えるクエリ集です。

この記事で解決できること

  • スキーマ内で容量トップ N のテーブルを探す SQL
  • テーブル本体・インデックス・LOB セグメントを合算した「実質サイズ」の取得
  • パーティションテーブルのパーティション別サイズ確認
  • 表領域横断で容量大のオブジェクトを探す方法
  • HWM(高水位標)と実データ量の乖離を確認する方法
  • サイズ削減(SHRINK/MOVE/TRUNCATE)へのつなぎ方
スポンサーリンク

前提知識:Oracle のセグメントとは

Oracle では、テーブルやインデックスはそれぞれ セグメント として表領域上に確保されます。DBA_SEGMENTS(または USER_SEGMENTS)ビューにセグメント名・種別・サイズが記録されており、これが「テーブルの容量を調べる」出発点になります。

セグメント種別(SEGMENT_TYPE) 内容
TABLE 通常テーブルの本体
TABLE PARTITION パーティションテーブルの各パーティション
TABLE SUBPARTITION サブパーティション
INDEX インデックス本体
INDEX PARTITION パーティションインデックスの各パーティション
LOBSEGMENT CLOB/BLOB/NCLOB 列のデータ領域
LOBINDEX LOB のインデックス領域
BYTES vs BLOCKS
DBA_SEGMENTS.BYTES はセグメントに割り当てられた総バイト数です。実際に使用中のデータ量ではなく、確保済みの領域(HWM 以下)を示します。DELETE 後にサイズが縮まないのはこのためです。

スキーマ内で容量が大きいテーブル上位を取得

自スキーマのテーブルサイズ上位 20 件

SELECT
  segment_name                        AS table_name,
  ROUND(bytes / 1024 / 1024, 2)       AS size_mb,
  ROUND(bytes / 1024 / 1024 / 1024, 3) AS size_gb,
  blocks,
  extents
FROM user_segments
WHERE segment_type IN ('TABLE', 'TABLE PARTITION', 'TABLE SUBPARTITION')
ORDER BY bytes DESC
FETCH FIRST 20 ROWS ONLY;

特定スキーマのテーブルサイズ上位(DBA 権限が必要)

SELECT
  owner,
  segment_name                        AS table_name,
  ROUND(bytes / 1024 / 1024, 2)       AS size_mb,
  ROUND(bytes / 1024 / 1024 / 1024, 3) AS size_gb,
  tablespace_name
FROM dba_segments
WHERE owner       = 'HR'
  AND segment_type IN ('TABLE', 'TABLE PARTITION', 'TABLE SUBPARTITION')
ORDER BY bytes DESC
FETCH FIRST 20 ROWS ONLY;
FETCH FIRST は Oracle 12c 以降
Oracle 11g 以前は FETCH FIRST ... ROWS ONLY が使えません。代わりに WHERE ROWNUM <= 20 を外側に付けるか、ORDER BY を使ったサブクエリで対応してください。
SELECT * FROM (...ORDER BY bytes DESC) WHERE ROWNUM <= 20;

テーブル本体+インデックス+LOB を合算した実質サイズ

テーブル本体だけでなく、そのインデックスや LOB セグメントも含めた 合計ディスク使用量 を把握するには、セグメント種別を横断して集計します。

インデックスや LOB が複数ある場合は UNION ALL 方式が確実
シンプルな JOIN ではインデックスや LOB が複数あるテーブルで集計が不正確になります。下記の UNION ALL を使った完全版を使ってください。
-- インデックス・LOB も含めた完全合算(推奨)
SELECT
  owner,
  table_name,
  ROUND(SUM(bytes) / 1024 / 1024, 2) AS total_mb
FROM (
  -- テーブル本体・パーティション
  SELECT owner, segment_name AS table_name, bytes
  FROM dba_segments
  WHERE segment_type IN ('TABLE','TABLE PARTITION','TABLE SUBPARTITION')
    AND owner = 'HR'
  UNION ALL
  -- インデックス
  SELECT i.owner, i.table_name, s.bytes
  FROM dba_indexes i
  JOIN dba_segments s
    ON s.owner = i.owner AND s.segment_name = i.index_name
  WHERE i.owner = 'HR'
  UNION ALL
  -- LOB セグメント
  SELECT l.owner, l.table_name, s.bytes
  FROM dba_lobs l
  JOIN dba_segments s
    ON s.owner = l.owner AND s.segment_name = l.segment_name
  WHERE l.owner = 'HR'
)
GROUP BY owner, table_name
ORDER BY total_mb DESC
FETCH FIRST 20 ROWS ONLY;

パーティションテーブルのパーティション別サイズ

パーティションテーブルでは、全体サイズより パーティション別の偏り を把握することが重要です。特定パーティションだけが肥大している場合、そのパーティションのみ TRUNCATE や EXCHANGE で対処できます。

-- パーティションテーブルの各パーティションサイズ
SELECT
  segment_name                         AS table_name,
  partition_name,
  ROUND(bytes / 1024 / 1024, 2)        AS size_mb,
  blocks,
  extents
FROM dba_segments
WHERE owner        = 'HR'
  AND segment_name = 'ORDERS'    -- テーブル名を指定
  AND segment_type = 'TABLE PARTITION'
ORDER BY bytes DESC;

-- パーティションテーブルを持つスキーマの全テーブルをパーティション別で集計
SELECT
  segment_name                         AS table_name,
  COUNT(*)                             AS partition_count,
  ROUND(SUM(bytes) / 1024 / 1024, 2)  AS total_mb,
  ROUND(MAX(bytes) / 1024 / 1024, 2)  AS max_partition_mb,
  ROUND(MIN(bytes) / 1024 / 1024, 2)  AS min_partition_mb
FROM dba_segments
WHERE owner       = 'HR'
  AND segment_type = 'TABLE PARTITION'
GROUP BY segment_name
ORDER BY total_mb DESC;

LOB セグメントが大きいテーブルを探す

CLOB/BLOB 列を持つテーブルでは、LOB データが別セグメントに格納されるため、テーブル本体のサイズだけ見ていると見落とします。

-- LOB セグメントサイズ上位(テーブル名・列名付き)
SELECT
  l.table_name,
  l.column_name,
  l.segment_name,
  ROUND(s.bytes / 1024 / 1024, 2)  AS lob_mb,
  s.tablespace_name
FROM dba_lobs l
JOIN dba_segments s
  ON s.owner = l.owner AND s.segment_name = l.segment_name
WHERE l.owner = 'HR'
ORDER BY s.bytes DESC;

表領域横断でディスク使用量が多いオブジェクトを探す

-- データベース全体でサイズ上位 30(DBA ロール必要)
SELECT
  owner,
  segment_name,
  segment_type,
  tablespace_name,
  ROUND(bytes / 1024 / 1024, 2)        AS size_mb,
  ROUND(bytes / 1024 / 1024 / 1024, 3) AS size_gb
FROM dba_segments
ORDER BY bytes DESC
FETCH FIRST 30 ROWS ONLY;

-- 表領域別・オーナー別の合計サイズ
SELECT
  tablespace_name,
  owner,
  ROUND(SUM(bytes) / 1024 / 1024 / 1024, 2) AS total_gb,
  COUNT(*)                                    AS segment_count
FROM dba_segments
GROUP BY tablespace_name, owner
ORDER BY total_gb DESC;

HWM(高水位標)と実データの乖離を確認する

DELETE を繰り返したテーブルは、HWM(High Water Mark)以下の空きブロックが多数残った状態になり、セグメントサイズが実データ量より大きく見えます。

-- テーブルの割当ブロック数 vs 実使用ブロック数の比較
SELECT
  t.table_name,
  t.num_rows,
  t.blocks                              AS used_blocks,      -- 実データが入ったブロック
  t.empty_blocks,                                            -- HWM 以下の空きブロック
  s.blocks                              AS allocated_blocks, -- 割当済み総ブロック数
  ROUND(t.blocks / NULLIF(s.blocks, 0) * 100, 1) AS usage_pct,
  ROUND(s.bytes / 1024 / 1024, 2)       AS segment_mb
FROM user_tables t
JOIN user_segments s ON s.segment_name = t.table_name
WHERE s.segment_type = 'TABLE'
  AND s.blocks > 1000   -- ある程度の大きさのテーブルに限定
ORDER BY (1 - COALESCE(t.blocks, 0) / NULLIF(s.blocks, 0)) DESC  -- 無駄率が高い順
FETCH FIRST 20 ROWS ONLY;
NUM_ROWS・BLOCKS は ANALYZE 後の値
user_tables.num_rowsblocks は統計情報収集(DBMS_STATS.GATHER_TABLE_STATS)が最近実行されていないと古い値になります。正確な乖離を確認する場合は先に統計を収集してください。
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES');

サイズ削減の方向性(確認後の次のアクション)

状況 推奨アクション コマンド例
DELETE 後に HWM が残っている SHRINK SPACE(オンライン) ALTER TABLE t SHRINK SPACE CASCADE;
SHRINK できない(ASSM 非対象等) MOVE(オフライン・インデックス再構築必要) ALTER TABLE t MOVE;
パーティション単位で削除したい TRUNCATE PARTITION ALTER TABLE t TRUNCATE PARTITION p1;
LOB が肥大化している LOB MOVE / SHRINK ALTER TABLE t MOVE LOB(col) STORE AS (TABLESPACE ts);
不要データを削除して再構成 DELETE + SHRINK または CTAS CREATE TABLE t_new AS SELECT ... FROM t WHERE ...;

よくある質問(FAQ)

Q USER_SEGMENTS と DBA_SEGMENTS の違いは?
A USER_SEGMENTS は自分が所有するセグメントのみ参照できます。DBA_SEGMENTS はデータベース内の全セグメントを参照でき、OWNER 列があります。自スキーマだけ調べる場合は USER_SEGMENTS、複数スキーマを横断する場合は DBA_SEGMENTS(DBA ロール必要)を使います。
Q DELETE してもテーブルサイズが減らないのはなぜ?
A Oracle の DELETE はデータブロック内の行を論理削除するだけで、セグメントに割り当てられたブロックは解放されません。HWM(高水位標)も下がりません。領域を物理的に返却するには ALTER TABLE ... SHRINK SPACE(ASSM 表領域)か ALTER TABLE ... MOVE(その後インデックス再構築)が必要です。TRUNCATE は HWM をリセットするため即座にサイズが解放されます。
Q DBA_SEGMENTS に出てこないテーブルがある。なぜ?
A Oracle では空のテーブル(行が 0 件)は領域が割り当てられず、DBA_SEGMENTS に登録されません。DBA_TABLES には存在するがセグメントがない状態です。1 行でも INSERT するとセグメントが作成されます。また、外部テーブル(EXTERNAL TABLE)もセグメントを持ちません。
Q BYTES と実際のディスク使用量が違うのはなぜ?
A DBA_SEGMENTS.BYTES は Oracle がそのセグメントに割り当てた領域サイズです。OS レベルのデータファイルサイズには表領域のフォーマット領域や他のセグメントの領域も含まれるため一致しません。表領域全体の使用量は DBA_DATA_FILESDBA_FREE_SPACE を使って確認します。
Q 圧縮テーブルのサイズはどう確認する?
A 圧縮テーブルも DBA_SEGMENTS.BYTES で実際の割当サイズが確認できます。圧縮されているかどうかは DBA_TABLES.COMPRESSION(ENABLED/DISABLED)と COMPRESS_FOR 列で確認できます。圧縮前後の推定サイズ比較には DBMS_COMPRESSION.GET_COMPRESSION_RATIO プロシージャを使います。

まとめ

調査目的 使用ビュー ポイント
スキーマ内テーブルサイズ上位 USER_SEGMENTS SEGMENT_TYPE = ‘TABLE’ でフィルタ
他スキーマ・全体横断 DBA_SEGMENTS DBA ロール必要。OWNER 列で絞り込み
インデックス・LOB 合算の実質サイズ DBA_SEGMENTS
+ DBA_INDEXES + DBA_LOBS
UNION ALL で 3 種を合算
パーティション別サイズ DBA_SEGMENTS SEGMENT_TYPE = ‘TABLE PARTITION’
HWM と実データの乖離 USER_TABLES + USER_SEGMENTS 統計収集後に BLOCKS 比較
LOB セグメント確認 DBA_LOBS + DBA_SEGMENTS LOBSEGMENT で JOIN

まず DBA_SEGMENTS でサイズ上位テーブルを特定し、次にインデックス・LOB を含む実質サイズを集計、HWM の乖離を確認するという 3 ステップで進めると効率的です。サイズ削減に進む前に必ず対象テーブルのバックアップと、SHRINK/MOVE の影響範囲(インデックス・制約)を確認してください。