インデックス(のみ)スキャン->ビットマップインデックススキャン->シーケンシャルスキャン
数行の場合、インデックススキャンを実行するのにお金がかかります。十分なデータページがすべてのユーザーに表示され(=十分にバキュームされ、同時書き込み負荷が多すぎない)、インデックスが必要なすべての列値を提供できる場合は、より高速なインデックスのみのスキャンが使用されます。より多くの行が返されると予想される場合(テーブルの割合が高く、データ分布、値の頻度、および行幅に応じて)、1つのデータページで複数の行が見つかる可能性が高くなります。次に、ビットマップインデックススキャンに切り替えるのが効果的です。 (または、複数の個別のインデックスを組み合わせる場合。)とにかくデータページの大部分にアクセスする必要がある場合は、シーケンシャルスキャンを実行し、余分な行をフィルタリングして、インデックスのオーバーヘッドを完全にスキップする方が安価です。
ランダムな順序でデータページにアクセスする方が、順番にアクセスするよりも(はるかに)高価ではない場合、インデックスの使用は(はるかに)安くなり、可能性が高くなります。これは、回転するディスクの代わりにSSDを使用する場合、またはそれ以上の場合はRAMにキャッシュされ、それぞれの構成パラメーターrandom_page_cost
およびeffective_cache_size
それに応じて設定されます。
あなたの場合、Postgresはシーケンシャルスキャンに切り替わり、rows=263962
が見つかることを期待しています。 、それはすでにテーブル全体の3%です。 (rows=47935
のみ 実際に見つかりました。以下を参照してください。)
この関連する回答の詳細:
- インデックスまたはビットマップインデックススキャンを使用したタイムスタンプでの効率的なPostgreSQLクエリ?
クエリプランの強制に注意
Postgresで特定のプランナーメソッドを直接強制することはできませんが、その他にすることはできます メソッドは、デバッグ目的では非常に高価に見えます。マニュアルのプランナーメソッドの構成を参照してください。
SET enable_seqscan = off
(別の回答で提案されているように)シーケンシャルスキャンに対してそれを行います。ただし、これはセッションでのデバッグのみを目的としています。 しない 自分が何をしているのかを正確に理解していない限り、これを本番環境の一般的な設定として使用してください。それはばかげたクエリプランを強制することができます。マニュアル:
これらの構成パラメーターは、クエリオプティマイザーによって選択されたクエリプランに影響を与える大まかな方法を提供します。特定のクエリに対してオプティマイザによって選択されたデフォルトのプランが最適でない場合、一時的 解決策は、これらの構成パラメーターの1つを使用して、オプティマイザーに別のプランを選択させることです。オプティマイザーによって選択されたプランの品質を向上させるためのより良い方法には、プランナーのコスト定数の調整(セクション19.7.2を参照)、
ANALYZE
の実行が含まれます。 手動で、default_statistics_target
の値を増やします 構成パラメーター、およびALTER TABLE SET STATISTICS
を使用して特定の列について収集される統計の量を増やす 。
それはすでにあなたが必要とするアドバイスのほとんどです。
- PostgreSQLが時々悪いクエリプランを選択しないようにします
この特定のケースでは、Postgresはemail_activities.email_recipient_id
で5〜6倍のヒットを期待しています。 実際に見つかったものよりも:
推定
rows=227007
vs.actual ... rows=40789
推定rows=263962
vs.actual ... rows=47935
このクエリを頻繁に実行する場合は、ANALYZE
を使用することで料金が発生します 特定の列のより正確な統計については、より大きなサンプルを参照してください。テーブルが大きい(約1,000万行)ので、次のようにします。
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
次に、ANALYZE email_activities;
最後の手段の測定
非常にまれな SET LOCAL enable_seqscan = off
を使用してインデックスを強制する場合 別のトランザクションまたは独自の環境を持つ関数で。いいね:
CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
RETURNS bigint AS
$func$
SELECT COUNT(DISTINCT a.email_recipient_id)
FROM email_activities a
WHERE a.email_recipient_id IN (
SELECT id
FROM email_recipients
WHERE email_campaign_id = $1
LIMIT $2) -- or consider query below
$func$ LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;
この設定は、関数のローカルスコープにのみ適用されます。
警告: これは概念実証にすぎません。このはるかに過激でない手動介入でさえ、長期的にはあなたを噛むかもしれません。カーディナリティ、値の頻度、スキーマ、グローバルなPostgres設定、すべてが時間とともに変化します。新しいPostgresバージョンにアップグレードします。現在強制しているクエリプランは、後で非常に悪い考えになる可能性があります。
そして通常、これはセットアップの問題に対する単なる回避策です。見つけて修正する方がよいでしょう。
代替クエリ
質問には重要な情報がありませんが、この同等のクエリはおそらくより高速で、(email_recipient_id
のインデックスを使用する可能性が高くなります )-より大きなLIMIT
の場合はますますそうなります 。
SELECT COUNT(*) AS ct
FROM (
SELECT id
FROM email_recipients
WHERE email_campaign_id = 1607
LIMIT 43000
) r
WHERE EXISTS (
SELECT FROM email_activities
WHERE email_recipient_id = r.id);