5.

SQLでインデックスを作成する方法|CREATE INDEXの使い方と注意点

編集

インデックスは、CREATE INDEX 文でテーブルの特定の列に作成する検索用のデータ構造で、対象の列を条件にした検索を高速化するために使う。基本構文は CREATE INDEX インデックス名 ON テーブル名 (カラム名); である。

この記事の要点
  • インデックスは検索を速くする「索引」。CREATE INDEX 名前 ON テーブル (列); で作成する。
  • 速くなるのは 読み取り(SELECT の検索・結合・並べ替え)。引き換えに 書き込み(INSERT / UPDATE / DELETE)が遅くなり、ディスク容量も増える
  • 複数列をまとめる複合インデックスでは 列の順序が決定的に重要。先頭列から順に絞り込みに使われる。
  • 重複を許さない場合は CREATE UNIQUE INDEX、不要になったら DROP INDEX で削除する。
  • 列に関数や演算を適用した条件(例: WHERE UPPER(name) = ...)や、前方一致でない LIKE '%xxx' ではインデックスが効きにくい。
  • カーディナリティ(値の種類)が低い列への単純なインデックスや、貼りすぎは効果が薄く逆効果になりうる。
  • 細かな構文・既定の挙動は RDBMS(MySQL / PostgreSQL / Oracle / SQL Server など)によって差があるため、利用環境の公式ドキュメントで確認すること。

インデックスとは

インデックス(index、索引)は、本の巻末にある索引と同じ役割を果たす仕組みである。索引がなければ目的の語を探すのに本を最初から1ページずつめくる必要があるが、索引があれば「この語は何ページ」と一気にたどり着ける。データベースでも同じで、インデックスがない列を条件に検索すると、テーブルの全行を先頭から順に読むフルテーブルスキャンが発生する。行数が増えるほどこの走査は遅くなる。

インデックスを作成すると、対象列の値が並べ替えられた状態の専用データ構造(多くのRDBMSで B-Tree と呼ばれる木構造)が別途用意される。検索時はこの構造をたどることで、全行を見ずに目的の行へ短い手数で到達できる。これが検索高速化の基本的な仕組みである。

重要なのは、インデックスは「検索を速くするための追加の仕組み」であって、データそのものではないという点だ。元のテーブルとは別に格納領域を消費し、テーブルのデータが変わるたびにインデックス側も更新する必要がある。この性質が、後述するメリットとデメリットの根拠になる。

CREATE INDEX 構文と例

インデックスを作成する基本構文は次のとおり。

CREATE INDEX インデックス名 ON テーブル名 (カラム名);

たとえば、ユーザー情報を持つ users テーブルの email 列でよく検索するなら、次のように作成する。

CREATE INDEX idx_users_email ON users (email);

これにより、次のような検索が高速化される。

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

インデックス名は同じテーブル(環境によってはスキーマやDB全体)で一意である必要がある。慣習として idx_テーブル名_列名 のように、対象が分かる命名にしておくと管理しやすい。なお、主キー(PRIMARY KEY)や UNIQUE 制約を付けた列には、多くのRDBMSで自動的にインデックスが作成されるため、改めて手動で作る必要はない。

複合インデックス(列順の重要性)

複数の列をまとめて1つのインデックスにしたものを複合インデックス(マルチカラムインデックス)と呼ぶ。複数列を組み合わせた条件で検索・並べ替えする場合に有効だ。

CREATE INDEX idx_orders_user_date ON orders (user_id, created_at);

複合インデックスで最も注意すべきは列の順序である。インデックスは指定した列の順に並べ替えられているため、原則として「左側(先頭)の列から順に」絞り込みに使われる。上の例(user_id, created_at の順)では次のように効き方が変わる。

  • WHERE user_id = 10 AND created_at >= '2026-01-01' … 両方の列が活用され、よく効く。
  • WHERE user_id = 10 … 先頭列だけでも効く。
  • WHERE created_at >= '2026-01-01' … 先頭の user_id を飛ばして2番目の列だけを条件にしても、このインデックスは効きにくい(先頭列が条件にないため)。

つまり、よく使う検索条件・等価比較で使う列を先頭に置くのが基本方針となる。(A, B)(B, A) はまったく別物として扱われる点に注意したい。具体的にどの順序が最適かはクエリの実行計画(後述)で確認するのが確実である。

UNIQUE INDEX(重複を許さないインデックス)

列の値の重複を許したくない場合は UNIQUE INDEX を使う。検索の高速化に加えて、同じ値が二重に登録されることを防ぐ制約としても機能する。

CREATE UNIQUE INDEX uq_users_email ON users (email);

この状態ですでに存在する email と同じ値を INSERT / UPDATE しようとすると、エラーになって弾かれる。複数列に対して CREATE UNIQUE INDEX uq_xxx ON テーブル (列1, 列2); とすれば、「列1と列2の組み合わせ」が一意であることを保証できる。

なお NULL の扱いはRDBMSによって差がある。一般に多くのRDBMSでは複数の NULL を重複と見なさず許容するが、挙動が異なる製品・設定もあるため、NULL を含む列に UNIQUE を付ける場合は利用環境の仕様を確認すること。

インデックスの削除(DROP INDEX)

不要になったインデックスは DROP INDEX で削除する。ただし、この構文にはRDBMSによる差が大きいので注意が必要だ。

-- PostgreSQL / Oracle など(インデックス名のみで削除)

DROP INDEX idx_users_email;

 

-- MySQL(対象テーブルの指定が必要)

DROP INDEX idx_users_email ON users;

-- MySQL では ALTER TABLE 形式も使える

ALTER TABLE users DROP INDEX idx_users_email;

このように、MySQL では ON テーブル名 が必要で ALTER TABLE 形式も使えるのに対し、PostgreSQL や Oracle ではインデックス名だけで削除する。使っているRDBMSの構文に合わせること。

インデックスが効くケース・効かないケース

インデックスは「作れば必ず速くなる」ものではない。クエリの書き方によって効いたり効かなかったりする。代表的なパターンを整理しておく。

  • 効きやすい: WHERE 列 = 値 の等価比較、WHERE 列 BETWEEN ... AND ...< / > の範囲検索、JOIN の結合条件に使う列、ORDER BY / GROUP BY の対象列。
  • 前方一致の LIKE: WHERE name LIKE '田中%' のように末尾だけがワイルドカードなら効きやすい。
  • 効きにくい: WHERE name LIKE '%田中%' のように先頭がワイルドカードの中間・後方一致。
  • 効きにくい: 列に関数や演算を適用した条件。例として WHERE UPPER(email) = 'A@B.COM'WHERE price * 1.1 > 1000 は、列 email / price 自体のインデックスが使われないことが多い(これを「インデックスが効かない=索引が無視される」状態と呼ぶ)。
  • 効きにくい: 検索対象が全体の大部分を占める場合。多くの行が該当するならフルスキャンの方が速いとオプティマイザが判断し、インデックスを使わないことがある。

実際にインデックスが使われているかは、実行計画を確認するのが最も確実だ。クエリの先頭に EXPLAIN(PostgreSQL では EXPLAIN ANALYZE も可)を付けて実行すると、フルスキャンかインデックス利用かを確認できる。構文や出力はRDBMSごとに異なる。

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

メリットとデメリットの比較

インデックスは「検索速度」と「書き込み・容量コスト」のトレードオフで考えるのが基本である。

観点 インデックスあり インデックスなし
検索(SELECT)の速度 該当列の条件検索・結合・並べ替えが速い 全行走査になりやすく、行数が増えるほど遅い
書き込み(INSERT/UPDATE/DELETE) インデックスの更新が伴うため遅くなる 追加の更新がなく速い
ディスク容量 インデックス用の領域を別途消費する テーブル分のみで済む
運用・保守 設計と見直し(不要インデックスの削除等)が必要 管理対象が少ない

要するに、読み取りを速くする代わりに、書き込みと容量にコストを払うのがインデックスである。読み取りが多いテーブルほど恩恵が大きく、書き込みが極端に多いテーブルでは慎重に判断する必要がある。

いつインデックスを貼るべきか

むやみに貼るのではなく、次のような列・状況を優先するとよい。

  • WHERE 句で頻繁に検索条件に使う列(特に等価比較で絞り込む列)。
  • JOIN の結合条件に使う列(多くは外部キーに相当する列)。
  • ORDER BY / GROUP BY でよく使う列
  • カーディナリティが高い列(値の種類が多く、条件で行をしっかり絞り込める列)。たとえばメールアドレスやユーザーIDなど。
  • 行数が多く、検索が遅いと感じるテーブル。

逆に、行数が少ないテーブルや、検索に使われない列、書き込みが集中する一時テーブルなどは、インデックスを付けても効果が薄い、あるいはコストが上回ることがある。

落とし穴

よくある落とし穴
  • 貼りすぎ: 「とりあえず全列にインデックス」は逆効果。INSERT / UPDATE / DELETE のたびに全インデックスの更新が走り、書き込みが目に見えて遅くなる。容量も無駄に増える。実際に使われるインデックスだけを残すのが鉄則。
  • 低カーディナリティ列への単純なインデックス: 性別フラグや「有効/無効」のように値の種類が2〜数種類しかない列に通常のインデックスを貼っても、多くの行が該当するため絞り込み効果が小さく、使われないことが多い。
  • 列への関数・演算で無効化: WHERE UPPER(name) = 'TARO'WHERE DATE(created_at) = '2026-06-12' のように列を加工すると、その列のインデックスは効かなくなりやすい。可能なら条件側ではなく値側を加工する、あるいは加工後の値に対応する仕組み(関数インデックスや生成列など、対応はRDBMSにより異なる)を検討する。
  • 暗黙の型変換: 文字列型の列に対して数値で比較する(あるいはその逆)と、内部で型変換が起きてインデックスが使われないことがある。条件の値の型は列の型に合わせる。
  • 作っただけで満足しない: 本当に使われているかは EXPLAIN で実行計画を確認する。使われていないインデックスはコストだけを残すので削除を検討する。

よくある質問(FAQ)

Q. インデックスを作るとデータの中身は変わりますか?
A. テーブルのデータそのものは変わりません。インデックスはあくまで検索を速くするための「索引」が別に追加されるだけで、行の値や件数は変化しません。削除(DROP INDEX)してもデータは消えず、検索が遅くなるだけです。

Q. インデックスはいくつでも作っていいですか?
A. 技術的には複数作れますが、増やすほど書き込みが遅くなり容量も増えます。実際の検索パターンで効くものに絞り、使われていないインデックスは削除するのが望ましいです。「多ければ速い」ではない点に注意してください。

Q. 主キーにはインデックスを別途作る必要がありますか?
A. 通常は不要です。多くのRDBMSでは PRIMARY KEY や UNIQUE 制約を定義した時点で自動的にインデックスが作成されます。同じ列に重ねて手動で作ると重複になるため避けましょう。

Q. CREATE INDEX や DROP INDEX の構文はどのデータベースでも同じですか?
A. 基本形は共通ですが、細部はRDBMS(MySQL / PostgreSQL / Oracle / SQL Server など)で異なります。特に DROP INDEX はテーブル指定の要否が分かれ、UNIQUE 列の NULL の扱いや、関数インデックス・部分インデックスなどの拡張機能の有無も製品によって違います。利用している環境の公式ドキュメントで確認してください。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. テーブル作成
  2. カラムの定義変更
  3. カラム名の変更
  4. カラム追加 / 削除
  5. インデックスの作成
  6. インデックスの削除
  7. AUTO_INCREMENTの仕様について
  8. AUTO_INCREMENTの追加と確認方法
  9. 外部キーの追加/削除と制約名の確認方法

最近更新/作成されたページ