「インデックス」タグアーカイブ

SQL:DELETEが異常に遅い原因と対処法まとめ|大量データで固まる時のチェックポイント

大量データを扱うシステムでは、
「DELETE文が異常に遅い」「1000件削除するだけで何分もかかる」
という現象は珍しくありません。

実際、筆者の環境でも

  • 1500万件のテーブル

  • DELETE 1件 = 約1.2秒

  • DELETE 100件 = 約2分

  • DELETE 1000件 = 約20分

という、明らかに正常とは言えない状態に遭遇しました。

この記事では、
SQL(特にOracle)で DELETEが異常に遅くなる原因と対処法
現場目線でわかりやすくまとめます。


1. DELETEが遅い原因は「処理そのものが重い」から

SELECTは一瞬で返るのに、DELETEだけ異様に遅い。
これは DELETE内部の処理コストが非常に重い ためです。

DELETEが行うことは主に以下:

  1. 対象行の削除(表領域から除去)

  2. インデックスから該当エントリの削除

  3. Undoログの生成

  4. Redoログの生成

  5. ロック管理

  6. トリガー処理(あれば)

このどれか、あるいは複数が詰まると
DELETEは1件ずつ遅くなり、数百件〜数千件だと“永遠に終わらない”状態になります。


2. DELETEが異常に遅いときの主な原因

2-1. インデックスの断片化・肥大化(最も多い)

大量INSERT / DELETE / UPDATE を繰り返したテーブルは
インデックスが “B-tree破損状態” になります。

症状:

  • DELETE 1件 = 1秒以上

  • 100件DELETEでも数分

  • SELECTは速い(インデックス検索は生きている)

これはインデックス劣化の典型。

→ インデックス再構築で劇的に改善することがあります。


2-2. Undo / Redo の処理が追いつかない

大量DELETEを連続してキャンセルすると

  • Undoセグメント肥大

  • Redoログ切替待ち

  • I/Oスラッシング

  • Undo segment extension 待ち

などが発生し、DELETEが異常に遅くなります。


2-3. 外部キー参照(子テーブル)が存在する

DELETEのたびに

  • 子テーブルを参照

  • 存在チェック

  • 参照整合性確認

を行うため、対象行が多いと極端に遅くなります。

※子テーブルを先に削除済なら影響なし。


2-4. トリガーが動いている

DELETEトリガーがあると
行ごとに処理が実行されるため、100行DELETEで100回 トリガーが動きます。

トリガー内でINSERT/UPDATEが走っていると、一気に重くなります。


2-5. テーブルやインデックスの断片化

特に1500万件級の大規模テーブルでは
断片化やI/O劣化によって DELETE が遅くなります。


2-6. ロックではない(SELECT が速ければ別原因)

「DELETEが遅い=ロック」と思われがちですが、
SELECTが瞬時に返るならロックではないことが多いです。

DELETE固有の処理が遅い証拠です。


3. DELETEが遅いときのチェックポイント(すぐ実行できる)

✔ ① SELECT COUNT は速い?

→ 速いならロックではない。

✔ ② 1件DELETEの時間を計測

→ 1秒以上かかるならインデックス劣化 or Undo詰まり。

✔ ③ インデックス数は多すぎない?

✔ ④ トリガーが無いか?

✔ ⑤ 子テーブルの外部キーは?

これだけでも原因の大半は特定できます。


4. 対策まとめ(実務で使える)

4-1. インデックス再構築(効果大)

DBA権限が必要ですが、再構築すると劇的に速くなります。


4-2. 統計情報の更新


4-3. 子テーブルを先に削除 or 一時的に外部キー無効化


4-4. トリガーの見直し / 一時無効化


4-5. DELETEを小分け(100件単位)で実行

大量DELETEは一度にやるのではなく
100件 or 1000件ごとに分割してCOMMITする方が安全。

※筆者環境では
100件DELETE = 約2分
1000件DELETE = 約20分
でした。


4-6. 一番速いのは「論理削除」

物理削除できないなら DELETE_FLG を立てる方法が最も安全。

これは DELETE より圧倒的に軽いです。


4-7. 本当に大量削除するなら「CTAS」が最強

残すデータだけ別テーブルにコピーして入れ替える方法。

DELETEより何倍も速いです。


5. まとめ

DELETEは単純なSQLに見えますが、
内部的には

  • インデックス更新

  • Undo/Redo 生成

  • ロック管理

  • 外部キー参照

  • トリガー処理

など多くの処理が絡むため
大量データでは極端に遅くなることがあります。

特に大規模テーブル(1000万件以上)だと
1件 DELETE でも1秒以上かかることが普通にあります。

もし DELETE が遅くて固まる場合は、

  1. SELECT が速いか確認

  2. 1件DELETEの時間を計測

  3. インデックス・Undo/Redo・トリガーを疑う

  4. 小分けDELETE or 論理削除に切り替える

  5. 必要に応じてDBAにインデックス再構築を依頼

といったステップで原因を切り分けると
安全で確実に改善できます。

SQL:実行計画(EXPLAIN PLAN)の読み方とボトルネックの見つけ方

データベースチューニングにおいて「どのSQLが遅いのか」だけでなく、「どの処理がボトルネックなのか」を正しく把握することは非常に重要です。
そのための基本ツールが**実行計画(EXPLAIN PLAN)**です。

本記事では、Oracleを例に実行計画の見方とボトルネックの探し方を、初心者でも理解できるように解説します。


✅ EXPLAIN PLANとは?

SQLを実行する際、Oracleが内部的に考える**最適な実行手順(アクセス方法)**を表示する機能です。

実行計画を見ることで、次のようなことがわかります。

  • テーブルにアクセスする順番

  • インデックスを使っているかどうか

  • 結合(JOIN)の方式

  • フルスキャンが走っているか

  • コスト(予測負荷)


✅ 実行計画の取得方法

▼ 方法1:EXPLAIN PLAN文を使う

▼ 方法2:SQL Developer で「実行計画」ボタン

GUI環境ではワンクリックで参照できます。


✅ Oracle実行計画の基本構造

実行計画は階層構造で、上から順に処理が行われます。
インデントが深いほど「その処理の中で実行される詳細処理」です。

例:


✅ よく出るOperationと解釈ポイント

Operation説明見どころ
TABLE ACCESS FULLテーブルの全件スキャン大量データで出たら要注意
TABLE ACCESS BY INDEX ROWIDインデックス参照後にROWIDアクセス最適パターンの一つ
INDEX UNIQUE SCAN主キー・ユニークインデックス検索高速
INDEX RANGE SCAN範囲検索効率的だが条件次第
HASH JOINハッシュ表でJOIN大量データ向き、メモリ消費
NESTED LOOPS小規模データに適したJOIN結合相手の行数が多いと遅い
SORT ORDER BY並び替え必要ならOK、無駄がないか確認

✅ ボトルネックの探し方

① TABLE ACCESS FULL に注意

  • 条件にインデックスが効いていない可能性

  • 大規模テーブルで特に危険

対策:

  • WHERE句に使う列にインデックス追加

  • 不必要なSELECT *を避ける

  • ファンクションインデックス


② JOIN方式を確認

JOIN方式特徴適したケース
NESTED LOOPS小テーブル to 大テーブルに◎OLTP向き
HASH JOIN大量データ向き、高速DWH向き
MERGE JOIN並び替え前提、ソート負荷ソート後結合

Nested Loops × 大量データ → 遅い可能性


③ コスト(COST)とROWSを確認

項目意味
ROWS見積もられる行数
COSTOracleが見積もる負荷指数
BYTESデータ量

COSTが極端に高い行がボトルネック候補。


④ SORTが多い場合

ORDER BY や DISTINCTが多いと遅くなる

対策:

  • 必要な場面以外でDISTINCT使用しない

  • ORDER BYの列にインデックス


✅ 実例:遅いSQLの典型パターン

問題点

  • UPPER(ENAME) → 関数でインデックス無効

  • LIKE ‘%〇〇’ → 前方ワイルドカードでインデックス無効

  • SELECT * → 不要な列読み込み

改善例


✅ チューニングの基本手順まとめ

ステップ内容
1実行計画を見る
2TABLE FULL SCANをチェック
3JOIN方法確認(Nested Loops vs Hash Join)
4コスト高い箇所を特定
5インデックス/SQL修正

✅ まとめ

  • EXPLAIN PLANはSQLの動作設計図

  • インデックス利用とJOIN方式を重視

  • FULL SCANと高コスト行は警戒

  • 必要な列だけ取得し、関数利用に注意

SQLチューニングは**「まず実行計画を見る」**がスタートです。
慣れるほど読み解きが早くなり、効率的な分析ができるようになります。

インデックスの仕組みを理解してSQLを劇的に高速化する方法

SQLの処理が遅いと感じたとき、多くの人が「サーバが遅いのでは?」と思いがちです。
しかし、実際の原因の多くは「インデックス(索引)」の使い方にあります。
この記事では、インデックスの基本構造から、実際のチューニング手法までを体系的に解説します。


1. インデックスとは?

インデックスとは、データベースが**検索を高速化するために作成する“索引”**のことです。
書籍の巻末索引のように、「この値はどこにあるか」を素早く見つけるための目次のような仕組みです。

🔹 例:インデックスなしの検索

このとき、インデックスが無ければ、データベースは全件を1件ずつ確認します(フルスキャン)。

🔹 例:インデックスありの検索

これにより、該当レコードを索引経由で一瞬で特定できるようになります。


2. インデックスの仕組みを理解する

🧩 B-treeインデックス

ほとんどのRDBMS(Oracle、MySQL、PostgreSQLなど)で採用されている構造です。
値が昇順に整理され、2分探索のように効率的に検索できます。

例えば「70」を探すとき、50より大きいので右に進み、次に70を発見します。
わずか2ステップで到達できるため、フルスキャンに比べて圧倒的に速いのです。


🧩 ビットマップインデックス(Oracleなど)

主に**値の種類が少ないカラム(性別、ステータスなど)**に有効です。
各値に対応するレコードのビットマップを管理することで、AND/OR検索が高速化します。


3. どんなカラムにインデックスを貼るべきか?

✅ 有効なケース

  • WHERE句で頻繁に検索される列

  • JOIN条件に使われる列

  • ORDER BYGROUP BYの対象列

  • 外部キー(FOREIGN KEY)列

🚫 不向きなケース

  • データ件数が極端に少ない列(例:性別など)

  • 更新頻度が高い列(INSERT/UPDATEが多いと再構築コストが増大)

  • テーブル全件を常に取得するクエリ


4. 実行計画で確認する

SQLの速度改善は、**「インデックスが使われているか」**を確認することが第一歩です。

結果例(MySQLの場合)

typekeyrowsExtra
refidx_users_email1Using index

「Using index」と表示されていれば、インデックスが利用されています。
逆に「ALL」となっている場合はフルスキャンです。


5. インデックスを使った高速化テクニック

🌟 複合インデックス(複数列)

複数の列を組み合わせた検索で効果を発揮します。
ただし、先頭の列が条件に含まれないと使われない点に注意が必要です。

例:


🌟 カバリングインデックス(Covering Index)

インデックスに必要な列すべてを含めることで、テーブルアクセスをスキップできます。

テーブルを参照せずにインデックスだけで完結するため、極めて高速です。


🌟 LIKE検索の最適化

前方一致(Yui%)はインデックスが有効ですが、

のような部分一致はインデックス無効です。
対策としては、**全文検索エンジン(MySQLのFULLTEXT、PostgreSQLのGIN/GiST)**を使う方法があります。


6. 注意点:インデックスの弊害

インデックスは便利ですが、万能ではありません。
特に以下の点には注意が必要です。

リスク説明
更新コスト増大INSERTやUPDATE時にインデックスも更新されるため、処理が重くなる
ストレージ消費大規模テーブルに多くのインデックスを張ると、容量が急増
メンテナンス負荷不要なインデックスを放置すると、統計情報がずれて性能が劣化

🧹 定期的に ANALYZE TABLEREBUILD INDEX を実施して、統計情報を更新しましょう。


7. 実践チューニング例

✏️ 例1:検索が遅いクエリ

🩹 改善策

✅ 実行計画の変化

  • 変更前:type = ALL(フルスキャン)

  • 変更後:type = ref(インデックス参照)

実行時間が数秒 → 数ミリ秒まで短縮されることもあります。


まとめ

ポイント内容
インデックスとはデータ検索を高速化するための“索引”
構造B-treeが主流。ビットマップは限定用途
効果的な付与検索条件、JOIN、GROUP BY、ORDER BY列
落とし穴更新負荷、容量増加、部分一致非対応
確認方法EXPLAINで実行計画を必ずチェック

🚀 結論

インデックスを理解すれば、SQLの速度は10倍以上高速化することも珍しくありません。
なんとなく作るのではなく、「どう検索されるか」を意識して設計することが重要です。

SQL:インデックスヒント(INDEX HINT)でクエリ最適化を行う方法

1. インデックスヒントとは?

SQLの**インデックスヒント(INDEX HINT)**とは、データベースに対して「このテーブルでは特定のインデックスを使用して実行してほしい」と明示的に指示するための構文です。
通常、DBエンジン(オプティマイザ)が自動で最適な実行計画を選びますが、統計情報が古い・複雑な条件句などの場合に誤ったインデックスを選ぶことがあります。

そのようなとき、開発者が明示的にインデックスを指定して最適化を誘導するのがINDEX HINTの目的です。


2. 一般的な書き方

Oracleの場合

MySQLの場合

SQL Serverの場合

データベースヒント指定方法備考
Oracle/*+ INDEX(テーブル名 インデックス名) */ヒント句はコメント形式
MySQLUSE INDEX (インデックス名)FORCE INDEX / IGNORE INDEX も可
SQL ServerWITH (INDEX(インデックス名))テーブルヒントとして指定

3. MySQLにおけるINDEX HINTの種類

種類意味使用例
USE INDEX指定したインデックスを優先的に使用USE INDEX (idx_col1)
FORCE INDEX指定インデックスを強制的に使用FORCE INDEX (idx_col1)
IGNORE INDEX指定インデックスを無視して検索IGNORE INDEX (idx_col1)

4. 実行計画を確認する

インデックスヒントを付与したら、実際に利用されているかを確認することが重要です。

MySQL

Oracle


チェック項目内容
key列(MySQL)使用されたインデックス名
access path(Oracle)INDEX RANGE SCAN などが表示される
実行コストコストが下がっているか確認

5. 使用上の注意点

注意点内容
1. 過信しないヒントは一時的なチューニングであり、将来的な統計情報変化で逆効果になることも。
2. 実行計画を常に確認ヒント適用後は EXPLAIN で確認すること。
3. SQL互換性に注意各DBMSで構文が異なるため、移植性が下がる。
4. ヒント指定よりも統計情報更新が基本統計情報を更新することで自動最適化が正しく働くことも多い。

6. 実用例:複合インデックスを明示的に使用

以下のように複数条件を持つ検索で、DBが誤ったインデックスを選ぶ場合に有効です。

このように、複合インデックスの指定順序に合わせてヒントを指定することで、不要な全件スキャンを防ぐことができます。


7. まとめ

ポイント内容
自動最適化が基本まずはDBエンジンに任せるのが原則
ヒントは最終手段特定クエリで誤選択時のみ使用
EXPLAINで検証効果を数値で確認
統計情報更新も忘れずにオプティマイザの精度を保つために重要