SQL / LESSON 16 / SQL16

SQL:インデックスとパフォーマンス

1学習の目的

2基礎解説

本の索引(さくいん)を思い浮かべてほしい。索引が無ければ、探したい言葉を1ページ目から最後までめくって探すしかない。INDEXはテーブルの索引で、特定の列の検索を劇的に速くする。

やりたいこと書き方効果
索引を作るCREATE INDEX idx_category ON products(category);その列の検索が速くなる
重複禁止の索引CREATE UNIQUE INDEX …索引と制約を兼ねる
索引を消すDROP INDEX idx_category;元の速度に戻る
実行計画を見るEXPLAIN QUERY PLAN SELECT …SCAN か SEARCH かが分かる
✅ 覚えるべき重要ポイント
  1. PRIMARY KEY には自動でインデックスが付くWHERE id = 3 のような検索が速いのはこのため。だからこそ、id以外の列で頻繁に検索するなら、自分でインデックスを追加する必要がある。
  2. EXPLAIN QUERY PLAN を付けると、実行される計画が分かる。SCAN全行を1つずつ確認(遅い)、SEARCH … USING INDEX索引を使って直接たどり着く(速い)。
  3. インデックスはタダではない。作れば検索は速くなるが、INSERTUPDATE のたびに索引も更新されるので、その分だけ書き込みは遅くなる。「よく検索される列」にだけ作るのが基本方針。
  4. WHEREJOINON で頻繁に使う列がインデックスの候補になる。ほとんど検索に使わない列に付けても効果は薄い
  5. UNIQUE INDEX索引と重複禁止制約を同時に持つ。SQL15のUNIQUE制約は、実は内部でこのインデックスを自動的に作っている。

現場使用例:ユーザーIDでの高速検索、メールアドレスでのログイン、注文日での絞り込み、外部キー列への索引付け。数百万件のテーブルで「検索が遅い」と言われたら、まずインデックスの有無を疑う

-- インデックス無しでの検索(実行計画を見る)
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE category = 'PC';
-- → SCAN products (全7行を1つずつ確認)

-- インデックスを作る
CREATE INDEX idx_category ON products(category);

-- 同じクエリをもう一度
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE category = 'PC';
-- → SEARCH products USING INDEX idx_category (category=?)
--   (索引を使って直接たどり着く)

-- 主キー検索は最初から速い(自動でインデックスがある)
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE id = 3;
-- → SEARCH products USING INTEGER PRIMARY KEY (rowid=?)

-- 不要になったら削除する
DROP INDEX idx_category;

3基本ドリル(10問)

SQL16-D01 products テーブルの category 列にインデックスを作るSQLを書け(名前は idx_category とする)。 コーディング★☆☆無料
期待される結果

idx_categoryが作成される

ヒント

CREATE INDEX idx_category ON products(category); と書く。

模範解答
CREATE INDEX idx_category ON products(category);
解説

「どのテーブルの」「どの列に」「何という名前で」を指定する。作成しただけでは何も変わらないが、以降その列での検索が速くなる。

SQL16-D02 EXPLAIN QUERY PLAN の結果に SCAN products と出たとき、何を意味するか。
A. インデックスを使って高速に検索した B. テーブルの全行を1つずつ確認した C. エラーが発生した D. 結果が0件だった
選択★☆☆無料
期待される結果

解答 == B

ヒント

「走査する」という意味の英単語。

模範解答
B
解説

SCANは「端から端まで見る」という意味。インデックスが無い列で検索すると、この方式になる。行数が少なければ問題にならないが、数百万件になると致命的に遅くなる。

SQL16-D03 idx_category という名前のインデックスを削除するSQLを書け。 コーディング★☆☆無料
期待される結果

idx_categoryが削除される

ヒント

DROP INDEX idx_category; と書く。

模範解答
DROP INDEX idx_category;
解説

インデックスの削除はテーブル自体には影響しない。データは残ったまま、索引だけが無くなり、検索速度が元に戻る。

SQL16-D04 実行計画を確認するコードを完成させよ。 穴埋め★☆☆無料
コード
____ QUERY PLAN
SELECT * FROM products WHERE price > 10000;
期待される結果

実行計画が表示される

ヒント

「説明する」という意味の7文字の英単語。

模範解答
EXPLAIN
解説

EXPLAIN QUERY PLAN を先頭に付けるだけで、そのSQLが実際にはどう実行されるかが分かる。デバッグや性能調査の第一歩。

SQL16-D05 主キー(PRIMARY KEY)の列で検索するとき、インデックスはどうなっているか。
A. 自分で作る必要がある B. 自動的に作られている C. インデックスは使えない D. 常にSCANになる
選択★☆☆無料
期待される結果

解答 == B

ヒント

products.id での検索は最初から速い。

模範解答
B
解説

PRIMARY KEY には自動でインデックスが付く。だから id での検索は何もしなくても速い。自分で作る必要があるのは、それ以外の列で頻繁に検索する場合。

SQL16-D06 products の price 列に idx_price という名前でインデックスを作り、その後 EXPLAIN QUERY PLAN で SELECT * FROM products WHERE price = 3200; の実行計画を確認するSQLを書け(2本)。 コーディング★★☆無料
期待される結果

インデックス作成後、SEARCHが使われる

ヒント

CREATE INDEXの後に、別のSQL文としてEXPLAIN QUERY PLANを書く。

模範解答
CREATE INDEX idx_price ON products(price);

EXPLAIN QUERY PLAN
SELECT * FROM products WHERE price = 3200;
解説

作成した直後から効果が反映される。実行計画がSCANからSEARCHに変わっていれば、インデックスが正しく使われている証拠。

SQL16-D07 name 列に重複を許さないインデックスを作るSQLを完成させよ。 穴埋め★★☆無料
コード
CREATE ____ INDEX idx_name ON products(name);
期待される結果

idx_nameが作成される

ヒント

「一意」を意味する6文字のキーワード(SQL15で学んだ制約と同じ単語)。

模範解答
UNIQUE
解説

UNIQUE INDEXは索引と重複禁止を同時に実現する。SQL15のUNIQUE制約は、内部的にはこの仕組みで動いている。

SQL16-D08 インデックスを作りすぎるとどうなるか。
A. 何も問題は起きない B. 検索は速くなり続けるが、INSERT/UPDATEは遅くなる C. 検索もINSERTも両方遅くなる D. データベースが壊れる
選択★★☆無料
期待される結果

解答 == B

ヒント

インデックスは検索用の索引。データが書き込まれるたびに、索引も更新する必要がある。

模範解答
B
解説

インデックスはタダではない。書き込みのたびに索引の更新コストがかかるので、「よく検索される列」にだけ絞って作るのが基本方針。全列に付けるのは逆効果になりうる。

SQL16-D09 products の category 列にインデックスを作り、そのインデックスの存在を sqlite_master から確認するSQLを書け(2本)。 コーディング★★☆無料
期待される結果

インデックスが一覧に表示される

ヒント

CREATE INDEXのあと、SELECT name FROM sqlite_master WHERE type='index'; で確認する。

模範解答
CREATE INDEX idx_category ON products(category);

SELECT name, tbl_name FROM sqlite_master WHERE type='index';
解説

sqlite_master はデータベースの構造そのものを保存しているテーブル。作成したテーブルやインデックスの一覧を、ここから確認できる。

SQL16-D10 products の stock 列にインデックスを作り、WHERE stock < 10 で検索したときの実行計画を確認するSQLを書け(2本)。 コーディング★★☆無料
期待される結果

インデックス作成後の実行計画が表示される

ヒント

作成 → EXPLAIN QUERY PLAN の順で2本書く。

模範解答
CREATE INDEX idx_stock ON products(stock);

EXPLAIN QUERY PLAN
SELECT * FROM products WHERE stock < 10;
解説

範囲検索(< や BETWEEN)にもインデックスは効く。完全一致だけでなく、大小比較でも索引を使って絞り込みができる。

4実践シナリオ(5問)

SQL16-S01 【検索速度の改善】customers テーブルの area 列は頻繁に検索条件として使われている。この列にインデックス(idx_area)を作るSQLを書け。 コーディング★★☆無料
期待される結果

idx_areaが作成される

ヒント

CREATE INDEX idx_area ON customers(area); と書く。

模範解答
CREATE INDEX idx_area ON customers(area);
解説

「エリア別の顧客一覧」を頻繁に検索するなら、この列にインデックスを作る価値がある。SQL09〜11で何度も使ったWHERE c.area='東京'のような検索が、これで速くなる。

SQL16-S02 【外部キー列への索引】orders の customer_id は頻繁にJOINの条件として使われる。この列にインデックス(idx_orders_customer)を作り、実行計画で確認するSQLを書け(2本)。 コーディング★★☆無料
期待される結果

インデックス作成後の実行計画が表示される

ヒント

CREATE INDEX idx_orders_customer ON orders(customer_id); のあと、EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id=1; を書く。

模範解答
CREATE INDEX idx_orders_customer ON orders(customer_id);

EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE customer_id = 1;
解説

JOINのON句で使われる列は、インデックスの最有力候補。SQL09〜11で書いた ON o.customer_id = c.id のような結合が、大量データでは大きく速くなる。

SQL16-S03 【インデックスの有無を比較する】products の name 列について、インデックス無しの状態と、作成した後の状態で、それぞれ WHERE name = 'マウス' の実行計画を確認するSQLを書け(3本:確認→作成→再確認)。 コーディング★★☆無料
期待される結果

1回目はSCAN、2回目はSEARCHになる

ヒント

EXPLAIN QUERY PLAN → CREATE INDEX → EXPLAIN QUERY PLAN の順に3本書く。

模範解答
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE name = 'マウス';

CREATE INDEX idx_name ON products(name);

EXPLAIN QUERY PLAN
SELECT * FROM products WHERE name = 'マウス';
解説

同じクエリでも、インデックスの有無で実行計画がSCANからSEARCHに変わるのを自分の目で確認できた。これがインデックスの効果を体感する最も直接的な方法。

SQL16-S04 【複数列の検討】price と stock の両方でよく検索される。どちらにもインデックスを作り、sqlite_master でproductsテーブルに関連するインデックスの数を数えるSQLを書け(3本)。 コーディング★★★無料
期待される結果

2つのインデックスが作成され、件数が確認できる

ヒント

2本のCREATE INDEXのあと、COUNT(*)で数える。

模範解答
CREATE INDEX idx_price ON products(price);

CREATE INDEX idx_stock ON products(stock);

SELECT COUNT(*) AS インデックス数
FROM sqlite_master
WHERE type='index' AND tbl_name='products';
解説

1つのテーブルに複数のインデックスを持たせられる。ただしD08で学んだとおり、増やすほど書き込みコストも増える。本当に必要な列だけに絞る判断が実務では求められる。

SQL16-S05 【不要になったインデックスの整理】かつて作った idx_category が、今はもう使われていないと判断された。これを削除し、削除後にsqlite_masterから存在しないことを確認するSQLを書け(3本:作成→削除→確認)。 コーディング★★★無料
期待される結果

削除後、idx_categoryが一覧に無い

ヒント

最初にCREATE INDEXしてから、DROP INDEXし、最後にSELECTで確認する。

模範解答
CREATE INDEX idx_category ON products(category);

DROP INDEX idx_category;

SELECT name FROM sqlite_master
WHERE type='index' AND name='idx_category';
解説

使われなくなったインデックスは削除するのが正しい運用。残しておいても検索は速くならず、書き込みコストだけが無駄にかかり続ける。

5仕上げ課題

SQL16-FINAL 【パフォーマンス改善プロジェクト】「注文検索が遅い」という報告を受けた。原因を調査し、改善せよ。

手順
① まず、顧客ID(customer_id)で注文を検索したときの現在の実行計画を確認する(SELECT * FROM orders WHERE customer_id = 1;
② 商品ID(product_id)で注文を検索したときの現在の実行計画も確認する
③ 両方の列にインデックスを作成する(idx_orders_customeridx_orders_product
④ ①②と同じ2つのクエリの実行計画を再度確認し、改善されたことを確かめる
⑤ 最後に、ordersテーブルに関連するインデックスの一覧を sqlite_master から取得する(type と name の2列)

期待される結果(⑤の出力)
type | name
index | idx_orders_customer
index | idx_orders_product


SQLは①→②→③→④(2本)→⑤の順に、合計6本を書くこと。
期待される結果

⑤の結果が一致し、①②はSCAN、④はSEARCHになる

ヒント

①②はEXPLAIN QUERY PLANでSCAN products/ordersが出るはず。③で2つのインデックスを作成。④で同じ2つのクエリを再実行するとSEARCHに変わる。⑤はSELECT type, name FROM sqlite_master WHERE type='index' AND tbl_name='orders';

模範解答
-- ① customer_id検索の現状確認
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE customer_id = 1;

-- ② product_id検索の現状確認
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE product_id = 1;

-- ③ インデックス作成
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_product ON orders(product_id);

-- ④ 改善後の確認
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE customer_id = 1;

EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE product_id = 1;

-- ⑤ インデックス一覧の確認
SELECT type, name
FROM sqlite_master
WHERE type='index' AND tbl_name='orders';
解説

これが実務での「遅いクエリの調査」の標準的な流れだ。①②で問題を確認し(SCANが出れば原因が分かる)、③で対策を打ち、④で本当に改善したかを検証する。「たぶん速くなっただろう」ではなく、実行計画という客観的な証拠で確認するのがプロの仕事の仕方になる。

orders テーブルは、SQL09〜13で何度もJOINの対象にしてきた。customer_idproduct_id はまさにJOINのON句で使われてきた列であり、ここにインデックスを張ることで、これまで書いてきた結合クエリすべてが速くなる。テーブル設計の最初の時点でこの2列にインデックスを用意しておくのが、実務では一般的な設計になる。

18章にわたって学んできたSQLは、ここでいったん一区切りとなる。SELECTでデータを見る力、WHEREとJOINで絞り込み結合する力、GROUP BYで集計する力、INSERT/UPDATE/DELETEで書き換える力、CREATE TABLEで設計する力、そしてINDEXで速くする力——これらすべてが揃って、初めて「実務で通用するSQL」と言える。

次章はいよいよ最終回。ここまでのすべてを使い、1つの大きな分析課題に取り組む。