SQLインデックス設計は遅いクエリから考える
SQLインデックス設計で迷う場面は、実務ではかなり多いです。どのカラムに貼るべきか。複合インデックスの順番はどうするか。WHERE句とORDER BYのどちらを優先するか。こうした判断は、単にカラムへindexを追加するだけでは決まりません。
結論から言うと、SQLインデックス設計は「遅いクエリの実行計画」を見て判断します。WHERE、JOIN、ORDER BY、取得件数、更新頻度をセットで見ることが重要です。
この記事では、SQLを使うバックエンドエンジニア向けに、インデックス設計で実務上よく見るポイントを整理します。PostgreSQLやMySQLで考え方が変わる部分もありますが、まずは共通する判断軸を押さえます。
SQLインデックスは検索条件を速くするための補助構造
まず、インデックスはテーブルとは別に持つ検索用の構造です。PostgreSQL公式のIndexesでも、行を高速に見つける手段として説明されています。一方で、データベース全体に追加コストも発生します。
例えば、usersテーブルからemailで1件を探す処理を考えます。emailにインデックスがなければ、テーブル全体を確認する可能性があります。
SELECT id, name, email
FROM users
WHERE email = 'sample@example.com';
この条件で頻繁に検索するなら、emailにインデックスを作る候補になります。
CREATE INDEX idx_users_email
ON users (email);
ただし、インデックスは読み取りだけを速くする魔法ではありません。INSERT、UPDATE、DELETEでは、テーブル本体だけでなくインデックス側の更新も必要です。そのため、使われないインデックスを増やすと問題が起きます。書き込み性能やストレージを圧迫するためです。
実行計画を見ずにSQLインデックスを増やさない
次に、実務では「遅いからインデックスを追加する」だけでは危険です。まず実行計画を見ます。PostgreSQLではUsing EXPLAINが公式に説明されています。MySQLではEXPLAIN Statementを確認できます。
実行計画では、テーブルを読む順番を確認します。どのインデックスが使われるかも見ます。さらに、どれくらいの行数を読む見込みかを確認します。MySQLならkeyやrowsを見ます。PostgreSQLならSeq Scan、Index Scan、cost、rowsなどを見ます。
EXPLAIN
SELECT id, user_id, status, created_at
FROM orders
WHERE user_id = 100
AND status = 'paid'
ORDER BY created_at DESC;
このSQLでorders全体を読んでいるなら、user_id、status、created_atをどう扱うかを考えます。一方で、すでに十分に絞れているなら、インデックス追加よりSQLや画面仕様の見直しが効く場合もあります。
WHERE句の等価条件をインデックス設計の起点にする
SQLインデックス設計では、まずWHERE句を見ます。特に、等価条件はインデックスが効きやすい条件です。
SELECT *
FROM orders
WHERE user_id = 100
AND status = 'paid';
このSQLが頻繁に実行され、かつ該当行が十分に絞り込めるなら、複合インデックスを検討します。
CREATE INDEX idx_orders_user_status
ON orders (user_id, status);
ただし、statusのように値の種類が少ないカラムだけでは、効果が薄いことがあります。例えば、paid、cancelled、pendingの3種類しかないケースです。paidが大半を占めるなら、多くの行を読むことになります。
そのため、単独で貼るより、user_idのように絞り込みやすい条件と組み合わせる方が自然です。インデックス設計では、検索条件の有無だけでなく、どれくらい絞れるかを見る必要があります。
複合インデックスはカラム順が重要になる
複合インデックスでは、カラムの順番が重要です。MySQL公式のMultiple-Column Indexesでも説明されています。複合インデックスは、先頭列、先頭から2列、先頭から3列のように使われます。
例えば、次のインデックスを考えます。
CREATE INDEX idx_orders_user_status_created
ON orders (user_id, status, created_at);
この場合、user_idだけの検索では使いやすいです。user_idとstatusの検索でも使いやすいです。さらに、user_idとstatusとcreated_atを使う検索にも合います。一方で、statusだけで検索するSQLでは、期待通りに使われない可能性があります。
PostgreSQL公式のMulticolumn Indexesでも、B-treeの複合インデックスでは先頭列が重要です。つまり、よく使うSQLのWHERE句に合わせて順番を決める必要があります。
| SQLの条件 | 使いやすさ | 理由 |
|---|---|---|
| user_id = ? | 高い | 先頭列を使う |
| user_id = ? AND status = ? | 高い | 先頭から2列を使う |
| status = ? | 低い | 先頭列を飛ばしている |
| status = ? AND created_at > ? | 低い | user_idが条件にない |
なお、これはDBMSやインデックス種類によって細部が変わります。したがって、最終判断は実行計画で確認します。
ORDER BYとLIMITもSQLインデックス設計で見る
検索条件だけでなく、ORDER BYも重要です。一覧画面では、絞り込み後に新しい順で表示することがよくあります。
SELECT id, user_id, status, created_at
FROM orders
WHERE user_id = 100
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
このSQLでは、user_idとstatusで絞り込み、created_atで並び替えています。こうした場合、次のような複合インデックスが候補になります。
CREATE INDEX idx_orders_user_status_created
ON orders (user_id, status, created_at DESC);
ただし、常にORDER BYまでインデックスへ含めるべきとは限りません。取得件数が少ない場合や、別の条件で十分に絞れている場合は、並び替えコストが問題にならないこともあります。
そのため、実務ではWHEREでどれだけ絞れるかを見ます。ORDER BYでソートが重いかも確認します。さらに、LIMITで何件返すかも見ます。単独のSQLだけでなく、画面でどの検索条件がよく使われるかも確認します。
JOIN条件に使うカラムも確認する
次に、JOIN条件もSQLインデックス設計で見落としやすいポイントです。親子テーブルを結合する場合、外部キー側のカラムにインデックスが必要になることがあります。
SELECT o.id, u.name, o.created_at
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid';
この例では、orders.user_idでusersと結合しています。大量データを扱う場合、JOIN条件に使うカラムがインデックスで支えられているかを確認します。
一方で、外部キー制約を作れば自動でインデックスが作られるかどうかは、DBMSや設定によって異なります。つまり、制約があるから大丈夫と決めつけず、実際のインデックス定義と実行計画を確認する必要があります。
SQLパフォーマンス改善では貼りすぎも問題になる
インデックスは増やせば増やすほど速くなるわけではありません。むしろ、貼りすぎると運用上の問題が増えます。
| 増えすぎたときの問題 | 実務で起きること |
|---|---|
| 書き込みが遅くなる | INSERT、UPDATE、DELETEでインデックス更新が増える |
| ストレージを使う | 大きなテーブルほどディスク使用量が増える |
| 設計意図が読みにくい | 似たインデックスが乱立し、どれが必要か分からない |
| 運用負荷が増える | 不要インデックスの調査や削除判断が難しくなる |
例えば、orders(user_id)とorders(user_id, status)があるとします。さらにorders(user_id, status, created_at)もある場合、本当に全部必要かを確認します。短いインデックスが別のSQLで使われていることもありますが、重複している可能性もあります。
なお、不要そうに見えてもすぐ削除するのは危険です。バッチ、管理画面、レポート、APIで使われている場合があります。普段見ていないSQLも確認しましょう。削除前には利用状況、実行計画、リリース後の影響範囲を確認します。
インデックス設計で避けたいアンチパターン
実務レビューでは、次のようなSQLインデックス設計を見つけたら注意します。
| アンチパターン | 起きやすい問題 | 見直すポイント |
|---|---|---|
| 遅いと言われたカラムにすぐ貼る | 根本原因を外す | 実行計画を先に見る |
| 全カラムに単独インデックスを貼る | 書き込みと運用が重くなる | 頻出SQLごとに必要性を判断する |
| 複合インデックスの順番を感覚で決める | 期待したSQLで使われない | WHERE、ORDER BY、JOIN条件を見る |
| 低選択度カラムだけに貼る | 多くの行を読むため効果が薄い | 他の条件との組み合わせを検討する |
| 本番データ量を考えない | 開発環境では速く、本番で遅くなる | 件数、分布、実行頻度を確認する |
特に、開発環境で問題が見えないケースは多いです。データが少ない環境では、全表走査でも十分速く見えます。そのため、実務では本番に近いデータ量や統計情報を前提に考える必要があります。
実務で見るSQLインデックス設計のチェックリスト
- 遅いSQLを特定しているか?
- EXPLAINやEXPLAIN ANALYZEで実行計画を確認したか?
- WHERE句の等価条件、範囲条件、LIKE条件を分けて見ているか?
- JOIN条件に使うカラムを確認したか?
- ORDER BYとLIMITの有無を確認したか?
- 複合インデックスのカラム順がSQLの使われ方と合っているか?
- 書き込み頻度やストレージ増加を考慮したか?
- 似たインデックスが重複していないか?
- 本番に近いデータ量で効果を確認したか?
まずはこのチェックリストで、インデックスを貼る前の確認をそろえます。そのうえで、実際に追加した後も実行計画を見ます。追加したインデックスが期待通り使われているかまで確認して、はじめて改善と言えます。
SQLインデックス設計でよくある質問
SQLのインデックスはどのカラムに貼るべきですか?
まず、頻繁に使われるWHERE句、JOIN条件、ORDER BYに出てくるカラムを確認します。ただし、出てくるだけでは不十分です。どれくらい絞り込めるか、実行頻度が高いか、書き込みへの影響が許容できるかも見ます。
複合インデックスと単独インデックスはどちらがよいですか?
よく一緒に使われる条件なら、複合インデックスが有効な場合があります。一方で、単独で使われるSQLが多いなら単独インデックスも必要です。どちらがよいかは、実際のSQLと実行計画で判断します。
LIKE検索でもインデックスは使えますか?
前方一致のLIKEでは使える場合があります。例えば、name LIKE ‘tanaka%’ のような条件です。一方で、name LIKE ‘%tanaka%’ のような中間一致では注意が必要です。通常のB-treeインデックスが効きにくいことがあります。検索要件によっては、全文検索や別の検索基盤を検討します。
まとめ:SQLインデックス設計は実行計画と業務要件で決める
SQLインデックス設計では、カラム名だけを見て判断しません。遅いSQLを特定します。次に、実行計画を確認します。WHERE、JOIN、ORDER BY、LIMIT、更新頻度をセットで見ます。
まずは、頻出する検索条件を整理します。次に、複合インデックスの順番をSQLの使われ方に合わせます。最後に、追加後の実行計画と運用負荷を確認します。この流れを守ると、場当たり的なインデックス追加を減らせます。
Java/Spring BootやWebアプリの実務では、SQLの読み方と実行計画が重要です。N+1問題やインデックス設計も切り離して考えることはできません。bluenaの採用情報では、バックエンドやSQL経験を活かせる案件相談も行っています。今の経験を次の環境でどう伸ばすか、一度整理してみてください。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
まとまっていなくてもOK——まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





