[説明の更新を反映してタイトルを更新]
Postgresql 9.6 を実行しています
期待するインデックスを使用していない複雑なクエリがあります。この小さな例に分解すると、インデックスが使用されていない理由がわかりません。
これらの例は、100 万レコードのテーブルで実行され、現在、すべてのレコードの列状態の値が「COMPLETED」になっています。状態はテキスト列で、btree インデックスがあります。
次のクエリでは、期待どおりにインデックスを使用しています。
explain analyze
SELECT * FROM(
SELECT
q.state = 'COMPLETED'::text AS completed_successfully
FROM request.request q
) a where NOT completed_successfully;
Ⅴ
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Index Only Scan using request_state_index on request q (cost=0.43..88162.19 rows=11200 width=1) (actual time=200.554..200.554 rows=0 loops=1)
Filter: (state <> 'COMPLETED'::text)
Rows Removed by Filter: 1050005
Heap Fetches: 198150
Planning time: 0.272 ms
Execution time: 200.579 ms
(6 rows)
しかし、自分のテーブルを参照する select に何か他のものを追加すると、プランナーは代わりに順次スキャンを実行することを選択します。
explain analyze
SELECT * FROM(
SELECT
q.state = 'COMPLETED'::text AS completed_successfully,
q.type
FROM request.request q
) a where NOT completed_successfully;
Ⅴ
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Seq Scan on request q (cost=0.00..234196.06 rows=11200 width=8) (actual time=407.713..407.713 rows=0 loops=1)
Filter: (state <> 'COMPLETED'::text)
Rows Removed by Filter: 1050005
Planning time: 0.113 ms
Execution time: 407.733 ms
(5 rows)
この単純な例でも同じ問題があります。
インデックスを使用:
SELECT
q.state
FROM request.request q
WHERE q.state = 'COMPLETED';
インデックスを使用しない:
SELECT
q.state,
q.type
FROM request.request q
WHERE q.state = 'COMPLETED';
[更新] (この場合) そこで使用しているインデックスは INDEX ONLY であることを理解しました。この場合、型もインデックスに含まれていないため、その使用を停止します。したがって、おそらく問題は、以下の「Not」の場合になぜそれを使用しないのかということです:
テーブルにない別の値を使用する場合、インデックスを使用することを知っています (これは理にかなっています)。
SELECT
q.state,
q.type
FROM request.request q
WHERE q.state = 'CREATED';
しかし、私がそうしなければ、そうではありません:
SELECT
q.state,
q.type
FROM request.request q
WHERE q.state != 'COMPLETED';
インデックスが使用されないのはなぜですか?
確実に使用するにはどうすればよいですか?
ほとんどの場合、このテーブルのほぼすべてのレコードが多くの最終状態 (IN 演算子を使用) のいずれかにあると思います。したがって、より複雑なクエリを実行するときは、これらのレコードがクエリのより高価な部分から早期かつ迅速に除外されることを期待しています。
[更新]
「NOT」は、サポートされている B ツリー操作ではないようです。ある種のユニークなアプローチが必要になります: https://www.postgresql.org/docs/current/indexes-types.html#INDEXES-TYPES-BTREE
次の部分インデックスを追加しようとしましたが、機能していないようです。
CREATE INDEX request_incomplete_state_index ON request.request (state) WHERE state NOT IN('COMPLETED', 'FAILED', 'CANCELLED');
CREATE INDEX request_complete_state_index ON request.request (state) WHERE state IN('COMPLETED', 'FAILED', 'CANCELLED');
この部分インデックスは機能しますが、理想的なソリューションではありません。
CREATE INDEX request_incomplete_state_exact_index ON request.request (state) WHERE state != 'COMPLETED';
explain analyze SELECT q.state, q.type FROM request.request q WHERE q.state != 'COMPLETED';
私もこの表現インデックスを試しましたが、理想的ではありませんでしたが、うまくいきませんでした:
CREATE OR REPLACE FUNCTION request.request_is_done(in_state text)
RETURNS BOOLEAN
LANGUAGE sql
STABLE
AS $function$
SELECT in_state IN ('COMPLETED', 'FAILED', 'CANCELLED');
$function$
;
CREATE INDEX request_is_done_index ON request.request (request.request_is_done(state));
explain analyze select * from request.request q where NOT request.request_is_done(state);
等号を持つ状態のリスト (In Clause) を使用すると機能します。したがって、NOT を使用しないようにするには、より大きなクエリを理解する必要があるかもしれません。