巨大テーブルの検索を劇的に速くする『遅延フェッチ』戦略
目次
- 基礎知識:インデックスとI/Oの仕組み
- 通常の検索と遅延フェッチの違い
- なぜ2段階取得が速いのか
- MySQLにおけるクラスタ化インデックス
- 遅延フェッチが効果的なケース
- まとめ:I/Oの観点から見た最適化
基礎知識:インデックスとI/Oの仕組み
※この章はデータベースの内部構造をざっくり理解するための補足です。
既に知っている人は読み飛ばしてください。
I/Oとは
I/O(Input / Output)は「データの読み書き処理」のこと。
データベースにおいては主に「ディスク ↔ メモリ」間のデータ転送を指します。
- メモリ:CPUに近く超高速。ただし容量が限られる。
- ディスク:容量は大きいが遅い。特にHDDではランダムアクセスが苦手。
SQLが遅くなる主な理由は「ディスクI/Oの多さ」。
つまり、どれだけディスクを読まずに済むか が性能の鍵です。
インデックスはB+Tree構造
インデックスは「検索を速くするためのデータ構造」。
MySQLやPostgreSQLでは多くの場合、B+Tree が使われています。
[ルートページ]
↓
[中間ページ]
↓
[リーフページ] → テーブル本体のポインタを持つ
- ルートページ:常にメモリにキャッシュされている
- 中間・リーフページ:ディスクに存在し、必要に応じて読み込まれる
インデックス探索はキー順で木をたどるため、連続アクセス(順次I/O) になりやすく高速です。
テーブル本体アクセスはランダムI/Oになりやすい
インデックスから得た「ポインタ(主キーなど)」を使って
テーブル本体の該当行を読み込むとき、
データの位置がバラバラだとアクセスもバラバラになります。
これが ランダムI/O。
1件ずつ異なる場所を読むため、I/Oコストが高くなります。
| 種類 | 特徴 |
|---|---|
| 順次I/O | 連続したブロックをまとめて読み込む(速い) |
| ランダムI/O | ディスク上を飛び回るように読む(遅い) |
クラスタ化インデックス(InnoDBの特徴)
MySQL(InnoDB)ではテーブル本体が 主キー順に物理的に並んで保存 されています。
これを「クラスタ化インデックス(clustered index)」と呼びます。
そのため、id IN (...) のIDを 主キー順に並べてアクセス すれば、
物理的にも近い領域をまとめて読み込めて、順次I/O化→高速化 が期待できます。
カバリングインデックスとは?
カバリングインデックスとは、クエリに必要な全カラムをインデックスだけでまかなえる状態のこと。
→ テーブル本体を読まずに結果を返せるので、I/Oをさらに減らせる。
通常の検索と遅延フェッチの違い
通常の検索
SELECT * FROM users WHERE age > 20 ORDER BY created_at DESC LIMIT 100;
条件検索と全カラム取得を一度に行う。
ただし、テーブル本体へのアクセスが発生しやすく、I/Oコストが高い。
遅延フェッチ(2段階取得)
SELECT id FROM users WHERE age > 20 ORDER BY created_at DESC LIMIT 100;
SELECT * FROM users WHERE id IN (...);
- 最初のクエリで インデックスだけ を使ってIDを集める
- 次のクエリで 必要な行だけ テーブルから取得する
この2段階に分けるだけで、I/Oを劇的に減らせる。
なぜ2段階取得が速いのか
インデックス効率の最大化
最初の段階では インデックスだけを読む ため、テーブル本体を読まない=ディスクI/Oをほぼゼロにできる。
カバリングインデックスがあれば、「検索+ソート+LIMIT」まですべてインデックス上で完結する。
例:
INDEX(age, created_at, id)
このインデックスがあれば、
最初の SELECT id ... クエリは インデックスのみで完了。
MySQLにおけるクラスタ化インデックス
MySQLのInnoDBでは主キー順で物理配置されるため、
SELECT * FROM users WHERE id IN (...)のIDを主キー順に並べて取得すれば、
物理的にも連続したデータブロックを順に読み込める。
結果:順次I/O化 による高速化が発生。
その他遅延フェッチが効果的なケース
| 状況 | 効果 |
|---|---|
| 取得件数が多いが詳細表示は一部だけ | IDリストをキャッシュしておき、必要なIDのみ詳細取得 |
| ページネーション | 最初にIDリストを取得しておけば、2ページ目以降が高速になる |
まとめ:I/Oの観点から見た最適化
- SQLのボトルネックは CPUではなくI/O
- インデックスだけで処理が完結すればディスクアクセスをほぼゼロにできる
- 遅延フェッチ戦略では
「必要なIDを先にまとめて取得 → 主キー順に実データを読む」ことで、
I/Oが少なく・キャッシュ効率の良い処理 を実現できる
Discussion