Day28 前半のゴール
「インデックスって“何者か”を、体感レベルで理解する」
ここまでで、かなり複雑な JOIN や集計を書けるようになりました。
次のテーマは パフォーマンス基礎:インデックス です。
Day28 前半のゴールはこうです。
インデックスが「速くなる魔法」ではなく、「検索用の専用データ構造」だと理解する
インデックスが効く場面・効かない場面のイメージを持つ
SQLite でインデックスを作る基本的な書き方を知る
まずは「仕組みをざっくり理解して、どんな効果があるかをイメージする」ところまで行きます。
細かい内部構造やチューニングは後半やその先の話です。
インデックスとは何か
「テーブルとは別に作る“検索専用の索引”」
インデックスを一言で言うと、
テーブルとは別に作る、“検索を速くするための索引”
です。
本棚をイメージすると分かりやすいです。
本が100冊あって、
「タイトルに『SQL』と付く本を探したい」とき、
毎回1冊ずつ開いて確認するのは大変です。
代わりに、「タイトル順に並んだ索引カード」があれば、
そこから一気に候補を絞り込めます。
テーブルが「本棚」だとしたら、
インデックスは「索引カード」のようなものです。
テーブル本体
行がそのまま順不同で並んでいる(論理的には順序なしと考える)
インデックス
特定のカラムの値をキーにして、
「この値の行はここにある」と素早くたどれるようにした構造
というイメージを持ってください。
インデックスがないとき、何が起きているか
「毎回“全件なめる”ことになる」
インデックスがない状態で、こんなクエリを実行するとします。
SELECT *
FROM customers
WHERE email = 'taro@example.com';
SQLインデックスがなければ、データベースは基本的に
テーブルの先頭から最後まで、1行ずつチェックする
という動きをします。
これを「全表走査(フルスキャン)」と呼びます。
行数が100件なら一瞬で終わりますが、
10万件、100万件と増えていくと、
毎回フルスキャンするのはかなり重くなります。
インデックスは、この「全部なめる」を避けるための仕組みです。
インデックスがあるとき、何が変わるか
「“探す場所”を一気に絞り込める」
同じクエリでも、email にインデックスがあると話が変わります。
CREATE INDEX idx_customers_email
ON customers(email);
SQLこのインデックスを作っておくと、
SELECT *
FROM customers
WHERE email = 'taro@example.com';
SQLを実行したとき、データベースは
まずインデックス(email の索引)を見て、taro@example.com に対応する行の場所を特定し、
その行だけをテーブル本体から取りに行く
という動きをします。
本棚の例で言えば、
索引カードで「SQL」と書かれたカードを探し、
そのカードに書かれた「棚の位置」を見て、
いきなりそこに手を伸ばす
という感じです。
これにより、
行数が増えても「探すコスト」が劇的に下がります。
PRIMARY KEY と UNIQUE は“インデックス付き”だと思っていい
「すでにあなたはインデックスの恩恵を受けている」
ここで一つ大事な事実があります。
PRIMARY KEY
UNIQUE
といった制約を付けたカラムには、
多くのデータベースで自動的にインデックスが作られます。
SQLite もそうです。
例えば、こういうテーブルを作ったとします。
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL
);
SQLこの場合、
id(PRIMARY KEY)email(UNIQUE)
の両方にインデックスが付きます。
だから、
SELECT *
FROM customers
WHERE id = 123;
SQLや
SELECT *
FROM customers
WHERE email = 'taro@example.com';
SQLは、すでにインデックスを使った高速な検索になっています。
つまり、
あなたは Day24 〜 25 の時点で、
無意識のうちにインデックスの恩恵を受けていた、ということです。
どんなカラムにインデックスを張るべきか
「“よく検索・絞り込みに使うカラム”が候補」
インデックスは「何でもかんでも張ればいい」わけではありません。
張るべきなのは、主にこういうカラムです。
WHERE でよく検索条件に使うカラム
JOIN の ON でよく使うカラム
ORDER BY や GROUP BY で頻繁に使うカラム(場合による)
例えば、売上管理のスキーマなら、
orders.customer_id
orders.ordered_at
order_items.product_id
あたりは、インデックス候補になります。
顧客別の売上をよく見るなら customer_id
期間別の集計をよくするなら ordered_at
商品別の集計をよくするなら product_id
といった具合です。
ここでの重要ポイントは、
「このカラムでよく絞り込む/つなぐ/並べるか?」
という視点でインデックスを考えることです。
インデックスにも“コスト”がある
「速くなる代わりに、“書き込み”が重くなる」
インデックスは便利ですが、タダではありません。
主なコストは二つです。
ディスク容量を食う
テーブルとは別に索引データを持つので、その分サイズが増える
INSERT / UPDATE / DELETE が遅くなる
行を追加・変更・削除するたびに、
インデックス側も更新しなければならない
つまり、
読み取り(SELECT)を速くする代わりに、
書き込み(INSERT / UPDATE / DELETE)が重くなる
というトレードオフがあります。
だからこそ、
「とりあえず全部のカラムにインデックス」
は悪手です。
よく検索に使うカラムに絞ってインデックスを張る
めったに使わない条件のためのインデックスは作らない
というバランス感覚が大事になります。
SQLite でのインデックス作成の基本形
「CREATE INDEX と DROP INDEX を押さえる」
SQLite でインデックスを作るのは、とてもシンプルです。
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
SQLこれで、orders.customer_id にインデックスが張られます。
名前(ここでは idx_orders_customer_id)は自分で決めます。
いらなくなったインデックスは、こう消せます。
DROP INDEX idx_orders_customer_id;
SQLテーブルを作り直さなくても、
後からインデックスだけ追加・削除できるのがポイントです。
実務では、
最初は最低限(PRIMARY KEY / UNIQUE だけ)で始める
実際に遅くなってきたクエリを見て、
必要なインデックスを追加していく
という流れが多いです。
セキュリティ・運用の視点から見たインデックス
「“どのカラムでよく検索されるか”は、情報の“価値”のヒントになる」
インデックスはパフォーマンスの話ですが、
セキュリティや運用の視点でも、少し面白い意味を持ちます。
例えば、
メールアドレスにインデックスがある
→ メールアドレスで頻繁に検索している(ログイン・照会など)
顧客IDにインデックスがある
→ 顧客単位の処理が多い
注文日時にインデックスがある
→ 期間集計やレポートが多い
というように、
「どのカラムでよく検索するか」
=「どの情報を軸にシステムが動いているか」
のヒントになります。
これは、
ログ監査やアクセス権限設計を考えるときにも役立ちます。
例えば、
メールアドレスで検索できる画面は、
「個人情報に直接アクセスできる画面」
とも言えます。
そういう画面には、
より強い認証や権限チェックが必要になります。
インデックスそのものはセキュリティ機能ではありませんが、
「どのカラムが“よく使われるキー”なのか」を教えてくれる、
という意味で、設計を考える材料になります。
Day28 前半のまとめ
インデックスは「テーブルとは別に作る検索専用の索引」で、フルスキャンせずに「探す場所」を一気に絞り込むための仕組み。
PRIMARY KEY や UNIQUE 制約のあるカラムには自動的にインデックスが付くことが多く、すでにあなたはその恩恵を受けている。
インデックスを張るべきなのは、WHERE・JOIN・ORDER BY・GROUP BY などで「よく検索・絞り込み・結合に使うカラム」であり、何でもかんでも張ると書き込みが重くなって逆効果になる。
SQLite では CREATE INDEX ... ON テーブル(カラム); で後からインデックスを追加でき、不要になったら DROP INDEX で消せるので、「まずは最小限 → 必要に応じて追加」という運用がしやすい。
どのカラムにインデックスが張られているかを見ると、「システムがどの情報を軸に動いているか」が見えてきて、ログ監査や権限設計を考えるうえでのヒントにもなる。
後半では、
実際にインデックスあり/なしでクエリの挙動を比べたり、
複合インデックスやインデックスが効かない書き方など、
もう一歩踏み込んだ“効かせ方・やらかしパターン”を見ていきます。
