PostgreSQL 10.3の使用。
CREATE TABLE tickets (
id bigserial primary key,
state character varying,
closed timestamp
);
CREATE INDEX "state_index" ON "tickets" ("state")
WHERE ((state)::text = 'open'::text));
テーブルには1027616行が含まれ、そのうち51533行はstate = 'open'
およびclosed IS NULL
、つまり5%です。
state
に条件を含むクエリは、期待どおりにインデックススキャンを使用して適切に実行されます。
explain analyze select * from tickets where state = 'open';
Index Scan using state_index on tickets (cost=0.29..16093.57 rows=36599 width=212) (actual time=0.025..22.875 rows=37346 loops=1)
Planning time: 0.212 ms
Execution time: 25.697 ms
条件closed IS NULL
を使用したクエリで同じかそれ以上のパフォーマンスを達成しようとしています。これにより、state
列を削除し、closed
列を使用して同じ行をフェッチできます。 closed
は、state = 'open'
と同じ行のnull
であるため、state
列は冗長です。
select * from tickets where closed IS NULL;
ただし、私が試したどのインデックスも、最初のクエリのように単一のインデックススキャンになりません。以下は、EXPLAIN ANALYZE
の結果とともに私が試したインデックスです。
部分インデックス:
CREATE INDEX "closed_index" ON "tickets" ("closed") WHERE (closed IS NULL)
explain analyze select * from tickets where closed IS NULL;
Bitmap Heap Scan on tickets (cost=604.22..38697.91 rows=36559 width=212) (actual time=12.879..48.780 rows=37348 loops=1)
Recheck Cond: (closed IS NULL)
Heap Blocks: exact=14757
-> Bitmap Index Scan on closed_index (cost=0.00..595.09 rows=36559 width=0) (actual time=7.585..7.585 rows=37348 loops=1)
Planning time: 4.831 ms
Execution time: 52.068 ms
式インデックス:
CREATE INDEX "closed_index" ON "tickets" ((closed IS NULL))
explain analyze select * from tickets where closed IS NULL;
Seq Scan on tickets (cost=0.00..45228.26 rows=36559 width=212) (actual time=0.025..271.418 rows=37348 loops=1)
Filter: (closed IS NULL)
Rows Removed by Filter: 836578
Planning time: 7.992 ms
Execution time: 274.504 ms
部分式インデックス:
CREATE INDEX "closed_index" ON "tickets" ((closed IS NULL))
WHERE (closed IS NULL);
explain analyze select * from tickets where closed IS NULL;
Bitmap Heap Scan on tickets (cost=604.22..38697.91 rows=36559 width=212) (actual time=177.109..238.008 rows=37348 loops=1)
Recheck Cond: (closed IS NULL)
Heap Blocks: exact=14757
-> Bitmap Index Scan on "closed_index" (cost=0.00..595.09 rows=36559 width=0) (actual time=174.598..174.598 rows=37348 loops=1)
Planning time: 23.063 ms
Execution time: 241.292 ms
CREATE TABLE tickets (
id bigserial primary key,
state character varying,
closed timestamp,
created timestamp,
updated timestamp,
title character varying,
size integer NOT NULL,
comment_count integer NOT NULL
);
CREATE INDEX "state_index" ON "tickets" ("state")
WHERE ((state)::text = 'open'::text));
テーブルには1027616行が含まれており、51533行がstate = 'open'およびclosed IS NULL、または5%です。上記のように、state
列を削除しようとしています。代わりに、closed
列の条件を使用して同じ行をフェッチできるようにします。
state
列に条件があるクエリは、インデックススキャンを使用します。
explain analyze select id, title, created, closed, updated from tickets where state = 'open';
Index Scan using state_index on tickets (cost=0.29..22901.58 rows=49356 width=72) (actual time=0.107..49.599 rows=51533 loops=1)
Planning time: 0.511 ms
Execution time: 54.366 ms
closed
列のクエリに切り替えると、同じパフォーマンス(理想的にはインデックススキャン)が必要です。 id
とclosed IS NULL
で部分インデックスを試しました:
CREATE INDEX closed_index ON tickets (id) WHERE closed IS NULL;
VACUUM ANALYZE tickets;
explain analyze select id, title, created, closed, updated from tickets where closed IS NULL;
Bitmap Heap Scan on tickets (cost=811.96..33999.42 rows=49461 width=72) (actual time=7.868..47.080 rows=51537 loops=1)
Recheck Cond: (closed IS NULL)
Heap Blocks: exact=17479
-> Bitmap Index Scan on closed_index (cost=0.00..799.60 rows=49461 width=0) (actual time=4.868..4.868 rows=51537 loops=1)
Planning time: 0.222 ms
Execution time: 51.028 ms
中心的な情報を想定します。
行の約15%が_
state = 'open'
_および_closed IS NULL
_を含む
すべての1031584行の同じ15%がこれらの両方の条件を満たすことを意味します (すべての詳細が重要です!)。両方の条件は同等に実行する必要があります-155k行(!)
クエリプランには、37346の条件を満たす行が表示されます。15%ではなく3.6%です。あなたの質問ではまだ何かが正しくありません。
3.6%の場合、インデックスは意味を成すようになります。小さな行サイズは、行あたり最大52バイト、ページあたり約155行を占めます。完全にランダムな分布では、ページあたり5〜6ヒットになります。 Postgresはとにかくすべてのページを読み、順次スキャン最速の計画である必要があります。ミスのフィルタリングは、何らかの方法でインデックスを含めるよりも高速である必要があります。
通常、対象となる行は多かれ少なかれクラスター化されており、含まれるデータページの数が少ないほど、インデックスを含める意味が大きくなります。すべてのビットマップインデックススキャンですが、インデックススキャンのケースはほとんどありません。あなたが主張する15%の場合、はるかに少ない(「ほとんどない」よりもはるかに「少ない」ことができる限り)。
あなたのupdated数(すべての行の約5%が一致)については、インデックススキャンよりもビットマップインデックススキャンを期待しています。考えられる説明:大量のデッドタプルを含むテーブルブロート。書き込み負荷が高いとおっしゃいました。その結果、データページあたりのライブタプルが少なくなり、ビットマップインデックススキャンと比較して、インデックススキャンが優先されます。 _VACUUM FULL ANALYZE
_の後で最初のクエリを再テストする場合があります(テーブルに排他ロックをかける余裕がある場合!)。私の仮説が当てはまる場合、物理テーブルのサイズが大幅に縮小し、インデックススキャンではなくビットマップインデックススキャンが表示されます(より高速でもあります)。
より積極的なautovacuum
設定が必要になる場合があります。見る:
「式インデックス」と「部分式インデックス」は役に立ちません。実際のインデックス式として_closed IS NULL
_は必要ありません(ここでは常にtrue
です)。この式はコストを追加するだけで、利益はありません。
最初のプレーンな部分インデックスは、より便利なバリアントです。ただし、closed
をインデックス式として使用しないでください(ここでも、常にNULL
を使用します)。代わりに、他のクエリに役立つ可能性のある列を使用します。追加のコストとインデックスの膨張を避けるために、理想的には更新しないでください。主キー列id
は、他の有用なアプリケーションがない場合の自然な候補です。
_CREATE INDEX closed_index ON tickets (id) WHERE closed IS NULL;
_
または、id
が役に立たない場合は、代わりに定数を検討してください:
_CREATE INDEX closed_index ON tickets ((1)) WHERE closed IS NULL;
_
これにより、他の廃止されたバリアントのように実際のインデックス列が役に立たなくなりますが、追加のコストと依存関係がすべて回避されます。関連:
更新された質問に更新されます-問題の行に他に多くの書き込みがない場合にのみ意味があります(追加された列updated
と_comments_count
_は私を疑わせます)。
* id
とその他の関連列(few&small)を使用して部分インデックスをインデックス式として作成し、適切なクエリを使用してインデックスのみのスキャンを取得します。*
_CREATE INDEX closed_index ON tickets (id, title, created, updated)
WHERE closed IS NULL;
VACUUM ANALYZE tickets; -- just to prove idx-only is possible
SELECT id, title, created, updated
, NULL::timestamp AS closed -- redundant, rather drop it
FROM tickets
WHERE closed IS NULL;
_
_SELECT *
_は必要ありません。_closed IS NULL
_はWHERE
句で指定します。したがって、小さな部分インデックスを高速インデックスのみのスキャンで使用できます-前提条件を満たしていると想定します(そのため、可視性を更新するためにVACUUM
をスローしましたそこにマップします)。これはまれなケースで、全行の5%以上を読み取るクエリが(テーブル全体を含めるまでも)引き続きインデックスを喜んで使用します。
設計に冗長性があるようですが、簡略化が可能なはずです。
これはPostgres 9.6以降で機能します リリースノートを引用:
インデックスの
WHERE
句がインデックス付けされていない列を参照する場合、部分インデックスで index-only scan の使用を許可します(Tomasフォンドラ、堀口京太郎)たとえば、
CREATE INDEX tidx_partial ON t(b) WHERE a > 0
で定義されたインデックスは、_WHERE a > 0
_を指定し、a
を使用しないクエリによるインデックスのみのスキャンに使用できるようになりました。以前は、aがインデックス列としてリストされていないため、これは許可されていませんでした。
または、質問の情報が誤解を招く可能性があります。
関連:
これでインデックスのみのスキャンが表示されない場合は、VACUUM
を実行した直後でも、書き込みの負荷が高く、可視性マップがインデックスのみのスキャンを許可する状態になることはありません。 マニュアル。 または、VACUUM
がその仕事をしないようにするDBに別の問題があります。関連: