Q1インデックスオンリースキャンでテーブル本体へ戻らずに済むのはなぜですか。
インデックスオンリースキャン — テーブル参照を省くインデックス
この記事は、基礎から複雑なSQL,SQLチューニングまでSQLの実践的なスキルを1からマスターする「SQL入門講座の一部」です。
クエリが触る列をすべてインデックスに含めると、テーブル本体への戻りが消える「インデックスオンリースキャン」(カバリングインデックス)を解説します。成立条件、1 列でも欠けると崩れる挙動、WHERE の絞り込み列を含めて 1 本にまとめる設計を、EXPLAIN QUERY PLAN で確かめます。
テーブルルックアップを省く — インデックスオンリースキャン
通常のインデックス探索は、インデックスで対象の行を見つけたあと、その行の他の列を読むためにテーブル本体に戻って 1 行ずつ読み直します。
この戻りをテーブルルックアップ(インデックスからテーブル本体の行を引きに行く処理)と呼びます。
返す行が多いとこの戻りが積み重なり、コストになります。
クエリが参照する列をすべて含むインデックスを使うと、必要な値がインデックスの中だけで揃うため、テーブル本体に戻る必要がなくなります。
インデックスだけで結果を返すこの動きをインデックスオンリースキャンと呼びます(このような全列を含むインデックスを、英語ではカバリングインデックスとも呼びます)。
-- region だけを含むインデックスで region を集計する例
-- 参照列(region)が全部インデックスに入る → テーブル本体へ戻らない
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(region);
EXPLAIN QUERY PLAN
SELECT region, COUNT(*)
FROM perf_sales
GROUP BY region;
1 列でも欠けるとテーブル本体へ戻る
インデックスオンリースキャンが成立するのは、SELECT・WHERE・GROUP BY などクエリが触る列が 1 つ残らずインデックスに含まれているときだけです。
1 列でも足りないと、その列の値を読むためにテーブル本体へ戻る必要があり、計画からUSING COVERING INDEXが消えます。
たとえば(emp_id, amount)のインデックスに対してSELECT emp_id, SUM(amount), regionのようにregionを追加すると、regionはインデックスに無いためテーブル本体へ戻ります。
-- (region, amount) のインデックスに対し、
-- 参照列に product を足すとインデックスオンリースキャンが崩れる例
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(region, amount);
-- region, SUM(amount) だけ → インデックスのみで完結
EXPLAIN QUERY PLAN
SELECT region, SUM(amount) FROM perf_sales GROUP BY region;
-- product を足す → インデックスに無いので本体へ戻る
EXPLAIN QUERY PLAN
SELECT region, SUM(amount), MAX(product) FROM perf_sales GROUP BY region;
WHERE の絞り込み列も含めてインデックスオンリースキャンを保つ
インデックスに含めるべき列は SELECT に出てくるものだけではありません。
WHERE で絞り込む列もクエリが触る列なので、これもインデックスに含めて初めてテーブル本体に戻らずに済みます。
「絞り込み列 → 出力・集計列」の順で 1 本のインデックスにまとめると、絞り込みと値の取り出しが同じインデックス内で完結します。
たとえばWHERE region = 'East'で絞ってSUM(amount)を出すなら、(region, amount)のように絞り込み列regionを先頭、集計列amountを後ろにします。
regionで対象を絞り込みつつ、amountの値もインデックスから読めるため、本体への戻りが発生しません。
-- 絞り込み列(status) と集計列(amount) を 1 本にまとめる例
DROP INDEX IF EXISTS ix_demo;
CREATE INDEX ix_demo ON perf_sales(status, amount);
EXPLAIN QUERY PLAN
SELECT SUM(amount)
FROM perf_sales
WHERE status = 'pending';
理解度チェック
まずは1問ずつ答えてみましょう。
Q2(emp_id, amount)のインデックスがあるとき、インデックスオンリースキャンが崩れてテーブル本体へ戻るのはどのクエリですか。
Q3WHERE region = 'East'で絞りSUM(amount)を出すクエリでインデックスオンリースキャンにするインデックスとして適切なものはどれですか。