Python | DB・SQL:インデックス

Python
スポンサーリンク

概要(インデックスは「データベースの辞書の“索引”」)

インデックス(index)は、データベースにとっての「本の索引」です。
本の後ろにある五十音順の索引があると、目的の用語のページを一瞬で開けますよね。
インデックスは、まさにそれと同じことをテーブルの中でやっています。

インデックスがないと、データベースは「最初のページから最後のページまで全部めくりながら探す」ことになります。
インデックスがあると、「索引を見て、該当ページに一気にジャンプ」できます。

ここから、

インデックスが何をしているのか
どんなときに効くのか
どんなときに逆効果になるのか

を、初心者向けにかみ砕いて説明していきます。


まずは「インデックスなし」の世界をイメージする

全件なめる検索(フルスキャン)のイメージ

users テーブルを考えます。

users テーブル(100 万件あるとする)

id | name      | email
---+-----------+----------------------
 1 | Taro      | taro@example.com
 2 | Hanako    | hanako@example.com
...

ここで、次のクエリを実行するとします。

SELECT *
FROM users
WHERE email = 'taro@example.com';
SQL

インデックスがない場合、データベースはどうするか。
イメージとしてはこうです。

1 行目を見る → email が一致するか? → 違う
2 行目を見る → 一致するか? → 違う

100 万行目まで、ひたすらチェック

つまり、「全件を順番にチェックする」しかありません。
これをフルテーブルスキャン(全表走査)と呼びます。

データが少ないうちは問題になりませんが、
何十万件、何百万件と増えてくると、
この「全部なめる」方式は一気に重くなります。

本の索引がないとどうなるか、に置き換えて考える

本の中から「インデックス」という単語が出てくるページを探したいとします。
索引がなければ、1 ページ目から最後まで全部めくって探すしかありません。

これが「インデックスなしの WHERE 検索」です。

「ページ数が少ない本」ならまだいいですが、
「1000 ページある専門書」だと地獄ですよね。

データベースも同じで、
行数が増えるほど「索引の有無」が効いてきます。


インデックスありの世界(索引で一気にジャンプ)

email にインデックスを張ると何が変わるか

さきほどの users.email にインデックスを作ってみます。

CREATE INDEX idx_users_email
ON users(email);
SQL

これで、users テーブルとは別に、
「email だけをキーにした索引」が作られます。

イメージとしては、こんな表が裏でできる感じです。

インデックス(email → 行の場所)

email               | 行の場所
--------------------+---------
hanako@example.com  | users の 2 行目
taro@example.com    | users の 1 行目
...

実際にはもっと賢い木構造(B-tree など)で管理されていますが、
初心者のうちは「email と、その行がどこにあるかの対応表」と思っておけば十分です。

この状態で、さきほどのクエリを実行するとどうなるか。

SELECT *
FROM users
WHERE email = 'taro@example.com';
SQL

データベースは、まずインデックスを見ます。

インデックスの中から ‘taro@example.com’ を探す
対応する「行の場所」を知る
その場所に一気にジャンプして、users の該当行だけ読む

つまり、「全部の行をなめる」のではなく、
「索引を見て、必要な行だけをピンポイントで読む」動きになります。

これが、インデックスの本質です。


インデックスが効く典型パターン

WHERE でよく絞り込みに使う列

一番分かりやすいのは、WHERE で頻繁に使う列です。

例えば、次のようなクエリをよく打つとします。

SELECT *
FROM users
WHERE email = '...';
SQL

この場合、email にインデックスがあると、
毎回の検索が「索引ジャンプ」になり、
フルスキャンより圧倒的に速くなります。

同じように、

ユーザー ID で絞る
日付で絞る
ステータスで絞る

といった「よく条件に使う列」は、
インデックス候補になります。

JOIN の ON で使う列

JOIN でもインデックスは重要です。

例えば、users と orders を JOIN するとき、

SELECT ...
FROM users
JOIN orders
  ON users.id = orders.user_id;
SQL

orders.user_id にインデックスがあると、
JOIN のときに「対応する行を探す」処理が速くなります。

外部キーとして他テーブルを参照している列(user_id, product_id など)は、
JOIN で頻繁に使われるので、
インデックスを張っておくのが定石です。

ORDER BY や GROUP BY にも効くことがある

ORDER BY や GROUP BY でも、
インデックスがうまく使われると速くなります。

例えば、

SELECT *
FROM users
ORDER BY created_at DESC
LIMIT 10;
SQL

created_at にインデックスがあると、
「新しい順に並んだ状態の索引」から先頭 10 件を取る、
という形で処理できることがあります。

ただし、ORDER BY とインデックスの関係は少し奥が深いので、
最初は「WHERE や JOIN で効く」と覚えておけば十分です。


インデックスの「代償」もちゃんと理解する

インデックスは「タダで速くなる魔法」ではない

ここがとても大事なポイントです。

インデックスは検索を速くしてくれますが、
その代わりに「書き込み(INSERT / UPDATE / DELETE)が重くなる」という副作用があります。

なぜかというと、

新しい行を INSERT したら、テーブルだけでなくインデックスにも登録しなければならない
既存の行のインデックス対象列を UPDATE したら、インデックスの中身も更新しなければならない
行を DELETE したら、インデックスからも削除しなければならない

からです。

つまり、

インデックスが多いほど、
「書き込みのたびにやること」が増える

ということになります。

「何でもかんでもインデックス」は逆効果

よくある失敗パターンが、

「検索が遅い → とりあえず全部の列にインデックスを張る」

というやり方です。

インデックスは 1 個 1 個が「索引用のデータ構造」なので、
数が増えるほど、

ディスク容量を食う
INSERT / UPDATE / DELETE が重くなる

というデメリットが積み上がります。

だからこそ、

本当に検索や JOIN でよく使う列だけに絞ってインデックスを張る

という「選別」がとても重要になります。


単一カラムインデックスと複合インデックス

単一カラムインデックス(1 列だけの索引)

これまでの例は、すべて「1 列だけのインデックス」でした。

CREATE INDEX idx_users_email
ON users(email);
SQL

これは、「email だけをキーにした索引」です。

WHERE email = ‘…’
WHERE email LIKE ‘abc%’

のようなクエリで効きます。

複合インデックス(複数列をまとめた索引)

現場では、「複数の列の組み合わせ」で検索することも多いです。

例えば、次のようなクエリをよく使うとします。

SELECT *
FROM orders
WHERE user_id = 1
  AND status = 'paid';
SQL

このとき、複合インデックスを張ることができます。

CREATE INDEX idx_orders_user_status
ON orders(user_id, status);
SQL

これは、「user_id → status の順で並んだ索引」です。

ここで重要なのが、「左から順に効く」という性質です。

このインデックスは、

WHERE user_id = ?
WHERE user_id = ? AND status = ?

には効きますが、

WHERE status = ?

だけだと効きにくい(または効かない)ことが多いです。

複合インデックスは、

左から順に条件に使われるときに最大限力を発揮する

という性質を持っています。


インデックスが効きにくい・効かないパターン

先頭に % が付いた LIKE

例えば、email にインデックスがあるとして、

WHERE email LIKE 'taro%'
SQL

これは、「taro で始まるメールアドレス」を探すクエリです。
多くのデータベースでは、インデックスをうまく使えます。

一方、

WHERE email LIKE '%@example.com'
SQL

のように、先頭に % が付いていると、
「どこに @example.com が出てくるか分からない」ため、
インデックスが効きにくくなります。

本の索引で、「3 文字目が ‘a’ の単語を探せ」と言われるようなものです。
先頭が決まっていないと、索引の構造を活かしにくいのです。

計算や関数をかけた列

例えば、created_at にインデックスがあるとして、

WHERE created_at >= '2025-01-01'
SQL

これはインデックスが効きやすいです。

一方、

WHERE DATE(created_at) = '2025-01-01'
SQL

のように、列に関数をかけてしまうと、
インデックスが効かなくなることがあります。

created_at の生の値ではなく、
DATE(created_at) という「変換後の値」で比較しているからです。

最初のうちは、「インデックス列に関数をかけると遅くなりがち」と覚えておけば十分です。


実務的な「インデックスを考える順番」

まずは「どんなクエリが多いか」を見る

インデックスを考えるとき、
いきなり「どの列に張ろうかな」と考えるのではなく、

どんな SELECT がよく実行されているか
どんな WHERE 条件が多いか
どんな JOIN が多いか

という「クエリ側」から考えるのが筋が良いです。

アプリのコードを見て、

ユーザーを email でよく検索している
注文を user_id と status でよく絞っている
日付範囲での検索が多い

といったパターンを洗い出し、
それに合わせてインデックスを設計します。

「遅いクエリ」を見つけてから、ピンポイントで張る

いきなり全部にインデックスを張るのではなく、

実際に遅くなっているクエリ
今後確実に重くなりそうなクエリ

を特定してから、
そのクエリに効くインデックスをピンポイントで張る、
というアプローチが現実的です。

そのとき、

WHERE で使っている列
JOIN の ON で使っている列
ORDER BY / GROUP BY で使っている列

を中心に考えると、
「効くインデックス」を設計しやすくなります。


まとめ(インデックスは「速さ」と「重さ」のトレードオフを設計する道具」)

インデックスを初心者目線で整理すると、こうなります。

インデックスは「テーブルとは別に持つ索引」で、WHERE や JOIN での検索を速くするための仕組み。
よく条件に使う列(email、user_id、日付など)にインデックスを張ると、フルスキャンせずに「索引ジャンプ」で探せるようになる。
その代わり、INSERT / UPDATE / DELETE のたびにインデックスも更新する必要があるので、「書き込みは重くなる」「ディスクも食う」というコストがある。
複合インデックスは「左から順に効く」ので、クエリの WHERE 条件の書き方とセットで設計する必要がある。
何でもかんでも張るのではなく、「実際によく使うクエリ」「遅くなっているクエリ」から逆算して、必要なものだけを選ぶのが大事。

タイトルとURLをコピーしました