products
、category
、およびscore
に複数列のインデックスを使用して、id
テーブルなどの行のページ分割と並べ替えを実装しています。
-- index
create index category_score_id on products(category, score, id)
where active = true;
-- selecting first page
select * from products where
category = 1
and active = true
order by score desc, id desc
limit 30;
-- selecting following pages
select * from products where
category = 1
and active = true
and (score, id) < (893, 102458) -- values from previous page
order by score desc, id desc
limit 30;
これは、インデックススキャンのみを使用して、期待どおりに完全に正常に機能します。
-- explain analyze of 2-nd query:
Limit (cost=0.00..99.52 rows=30 width=2591) (actual time=0.090..0.179 rows=30 loops=1)
-> Index Scan Backward using category_score_id on products (cost=0.00..76435.07 rows=23041 width=2591) (actual time=0.089..0.172 rows=30 loops=1)
Index Cond: ((category = 1) AND (ROW(score, id) < ROW(893, 102458)))
Total runtime: 0.085 ms
私が気になるのは次のクエリに追加のビットマップヒープスキャンが必要な理由:
select count(*) from products where
category = 1
and active = true;
-- explain analyze:
Aggregate (cost=49987.80..49987.81 rows=1 width=0) (actual time=104.847..104.847 rows=1 loops=1)
-> Bitmap Heap Scan on products (cost=538.85..49930.20 rows=23041 width=0) (actual time=8.919..98.952 rows=47937 loops=1)
Recheck Cond: ((category = 1) AND active)
Rows Removed by Index Recheck: 93006
-> Bitmap Index Scan on category_score_id (cost=0.00..533.09 rows=23041 width=0) (actual time=7.181..7.181 rows=47937 loops=1)
Index Cond: (category = 1)
Total runtime: 104.892 ms
extraビットマップスキャンを実行していません。ビットマップスキャンを実行していますの代わりに通常のインデックススキャン。
代わりにビットマップスキャンを実行するのはなぜですか?
その方が速くなると思っているからです。 LIMITとORDER BYがない場合、ビットマップスキャンを使用すると、より効率的な方法でテーブルヒープに対してIOが実行されます。PostgreSQLがこの評価で正しいかどうかを確認できます。一時的にset enable_bitmapscan TO off;
(キャッシング効果により、その実験を正しく解釈することが難しくなる場合があります。)
また、行:
Rows Removed by Index Recheck: 93006
Work_mem設定が小さすぎてビットマップを効率的に収容できない可能性があることを示唆しています。
Explain Planに1つではなく2つのエントリとして表示されるのはなぜですか?
通常のインデックススキャンは、実際には2つのスキャンを1つにまとめたもので、インデックスをスキャンしてから、テーブルをネストして検索します。ただし、これらの2つの部分は統合されており、実行プランの1つのエントリとしてのみ表示されます。
ビットマップケースにもこれらの2つのステップがありますが、統合されていません。 BitmapOr
やBitmapAnd
など、それらの間に追加のステップを挿入することが可能です。この可能性に対応するために、2つの個別のエントリとして報告されます。
通常、マルチカラムインデックスは適切に見えます。クエリのすべてまたはほとんどが_order by score desc, id desc
_を使用する場合、一致する並べ替え順序とより単純な条件で定義する方が少し効率的です。
_CREATE INDEX prod_category_score_id ON products(category, score DESC, id DESC)
WHERE active;
_
WHERE active = TRUE
_は、_WHERE active
_のより複雑な言い方です。Index Scan Backward
_は実質的にプレーンインデックススキャンと同じくらい高速ですが、multicolumnインデックスでソート順を一致させるとアクセスが簡単になります。Bitmap Heap Scan
_なのですか?Postgresの MVCCモデル のため、インデックス内のエントリが実際にテーブル内にまだ存在していることを確認する必要があります。
プランナーメソッド_Index Scan
_は、少数の行に最適です。 Postgresはインデックスから各行ポインタを取得し、ヒープ(テーブル)から行を順次フェッチします。これにより、行が現在のトランザクションから実際に見えるかどうかもチェックされます。
ヒット数が増えると、効果が低下します。同じデータページに複数の行が存在する場合、タプルを収集して同じページに1回だけアクセスする方が効率的です。これが_Bitmap Index Scan
_が本質的に(特に)行うことです。
count(*)
で行をカウントする場合、Postgresはヒープからのデータを必要としません。しかし、それでも各タプルが表示され、実際にカウントされることを確認する必要があります。 「遅いカウント」に関するWikiページ。
Postgres 9.2は_Index Only Scans
_を導入しました。 「可視性マップ」がページのすべてのタプルが現在のすべてのトランザクションから見えることを示している場合、Postgresは確認するためにヒープにアクセスする必要はありません。これは、インデックスを使用して最も効率的に決定される、ごく一部の行のみをカウントするクエリなどのクエリではより高速になります。
明らかに、多数の同時書き込み操作があります。または、自動バキューム設定が十分に積極的ではありません。そうしないと、より高速な_Index Only Scan
_が表示される場合があります。