Postgresに特定のインデックスを使用させるにはどうすればよいですか?


111

Postgresがインデックスを使用するように強制するにはどうすればよいですか?



1
+1この機能をぜひご覧ください。他の回答が言うように、単にseqスキャンを無効にするだけの問題ではありません。PGに特定のインデックスを使用させる機能が必要です。これは、実際の統計では統計情報が完全に間違っている可能性があり、その時点では信頼できない部分的な回避策を使用する必要があるためです。単純なケースでは最初にインデックスとその他の設定を確認する必要があることに同意しますが、ビッグデータの信頼性と高度な使用のためにこれが必要です。
collimarco

MySQLとOracleの両方にあります... Postgresのプランナーがそれほど信頼できない理由がわかりません。
Kevin Parker

回答:


103

多くのデータベースに見られる一般的な「インデックスヒント」機能について質問していると仮定すると、PostgreSQLはそのような機能を提供していません。これは、PostgreSQLチームによって行われた意識的な決定でした。その理由と代わりに何ができるかについての良い概要は、ここにあります。その理由は基本的に、PostgreSQLのオプティマイザーが統計に基づいて計画を再評価できるのに対して、データが変更されると後で問題が発生する傾向があるのはパフォーマンスハックであるためです。言い換えれば、今日の優れたクエリプランはおそらく、常に優れたクエリプランになるとは限らず、インデックスヒントは特定のクエリプランを常に強制します。

テストに役立つ非常に鈍いハンマーとして、enable_seqscanおよびenable_indexscanパラメータを使用できます。見る:

これらは、継続的な本番環境での使用には適しいません。クエリプランの選択に問題がある場合は、クエリパフォーマンスの問題を追跡するためのドキュメントを参照してください。enable_パラメータを設定して離れるだけではいけません。

インデックスを使用する十分な理由がない限り、Postgresが正しい選択をしている可能性があります。どうして?

  • 小さなテーブルの場合、順次スキャンを実行する方が高速です。
  • データ型が適切に一致しない場合、Postgresはインデックスを使用しません。適切なキャストを含める必要がある場合があります。
  • プランナー設定が問題を引き起こしている可能性があります。

この古いニュースグループの投稿も参照してください。


4
同意し、postgresにあなたのやり方でそれを強制することは、通常、あなたがそれを間違って行ったことを意味します。9/10プランナーが思いつくあらゆるものを打ち負かします。他の1回はあなたがそれを間違えたからです。
ケントフレドリック

インデックスホールドの本当に演算子クラスをチェックすることは良い考えだと思います。
メトス2012

2
私は古い質問を復活させたくありませんが、Postgresのドキュメント、ディスカッション、およびここで頻繁に見られますが、小さなテーブルに適したものについての一般的な概念はありますか?5000行、または50000などのようなものですか?
ワッフル2014

1
@wafflベンチマークを検討しましたか?インデックスと、ランダムなジャンクのn行でそれを埋めるための関数を備えた単純なテーブルを作成します。次に、nのさまざまな値のクエリプランを調べます。インデックスの使用を開始すると、大まかな答えが得られるはずです。また、PostgreSQLが(統計に基づいて)インデックススキャンによって非常に多くの行が削除されないと判断した場合にも、順次スキャンを取得できます。したがって、実際のパフォーマンスに関する懸念がある場合は、ベンチマークを行うことをお勧めします。偶然の逸話的な推測として、私は通常、数千は「小さい」と言います。
jpmc26 2014

9
Oracle、Teradata、MSSQLなどのプラットフォームで30年以上の経験があるため、PostgreSQL 10のオプティマイザーは特にスマートではありません。最新の統計を使用しても、特別な方向に強制されるよりも効率の悪い実行計画が生成されます。これらの問題を補うための構造的なヒントを提供することは、PostgreSQLがより多くの市場セグメントで成長することを可能にするソリューションを提供します。私見では。
Guido Leenders、

75

おそらく使用する唯一の正当な理由

set enable_seqscan=false

クエリを作成していて、テーブルに大量のデータがあった場合にクエリプランが実際にどうなるかをすばやく確認したい場合です。またはもちろん、データセットが小さすぎるという理由だけでクエリがインデックスを使用していないことをすばやく確認する必要がある場合。


41
この短い返答は実際にテストの目的のための良いヒントを与えます
dwery

3
誰も質問に答えていません!
Ivailo Bardarov 2014

@IvailoBardarovこれらすべての他の提案がここにある理由は、PostgreSQLにこの機能がないためです。これは、通常の使用方法とそれが引き起こす長期的な問題に基づいて開発者が行った意識的な決定でした。
jpmc26 2014

テストするための素晴らしいトリック:実行しset enable_seqscan=false、クエリを実行set enable_seqscan=trueし、すぐに実行してpostgresqlを適切な動作に戻します(明らかに、本番
環境で

2
@BrianHellekinより良い、SET SESSION enable_seqscan=false自分だけに影響を与えるため
イズカタ

19

PostgreSQLは、特定の条件に最適なインデックスを選択できない場合があります。例として、数百万行のトランザクションテーブルがあり、特定の日には数百行あるとします。テーブルには、transaction_id、client_id、date、descriptionという4つのインデックスがあります。次のクエリを実行するとします。

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQLは、transactions_date_idxではなく、transactions_description_idxインデックスを使用することを選択できます。これにより、クエリが1秒未満ではなく数分かかる場合があります。この場合、次のように条件を曖昧にすることで、日付のインデックスを強制的に使用できます。

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id

3
良いアイデア。ただし、この方法で現在のインデックスの使用を無効にすると、postgresqlクエリオプティマイザーは次の適切なインデックスにフォールバックします。したがって、オプティマイザがを選択する保証はありませんyour_wanted_index。代わりに、postgresqlエンジンがシーケンス/主キースキャンを実行するようにすることができます。結論-PostgreSqlサーバーのインデックス使用を強制する100%信頼できる方法はありません。
Agnius Vasiliauskas

where条件がなく、2つのテーブルまたは結合されていて、Postgresがインデックスを取得できない場合はどうなりますか。
Luna Lovegood

@Surya上記は、WHEREとJOINの両方に適用されます... ON条件
Ziggy Crueltyfree Zeitgeister

18

短い答え

この問題は通常、インデックススキャンの推定コストが高すぎ、現実を正しく反映していない場合に発生します。random_page_costこれを修正するには、構成パラメーターを下げる必要がある場合があります。Postgresのドキュメントから:

この値[...]を減らすと、システムはインデックススキャンを優先します。値を大きくすると、インデックススキャンが比較的高価に見えます。

より低い値が実際にPostgresにインデックスを使用させるかどうかを確認できます(ただし、これはテストのみに使用します):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

SET random_page_cost = DEFAULT;再びデフォルト値に戻すことができます。

バックグラウンド

インデックススキャンには、非順次ディスクページフェッチが必要です。Postgresはrandom_page_cost、シーケンシャルフェッチに関連するそのような非シーケンシャルフェッチのコストを推定するために使用します。デフォルト値はなので、シーケンシャルフェッチと比較4.0して平均コスト係数を4と想定しています(キャッシュの影響を考慮に入れて)。

ただし、問題は、このデフォルト値が次の重要な実際のシナリオでは不適切であることです。

1)ソリッドステートドライブ

ドキュメントが認めているように:

順次ドライブに比べてランダム読み取りコストが低いストレージ(ソリッドステートドライブなど)は、の値が小さいほど、より適切にモデル化できますrandom_page_cost

PostgresConf 2018での講演からのこのスライドの最後のポイントによると、ソリッドステートドライブのrandom_page_cost1.0および2.0ソリッドステートドライブに対して何かを設定する必要があります。

2)キャッシュされたデータ

必要なインデックスデータが既にRAMにキャッシュされている場合、インデックススキャンは常にシーケンシャルスキャンよりも大幅に高速になります。ドキュメントは言う:

同様に、データが完全にキャッシュにある可能性が高い場合は、[...]を減らすrandom_page_costことが適切な場合があります。

問題は、関連するデータがすでにキャッシュされているかどうかを簡単に知ることができないことです。ただし、特定のインデックスが頻繁に照会され、システムに十分なRAMがある場合は、データがキャッシュされる可能性が高いrandom_page_costため、低い値に設定する必要があります。さまざまな値を試して、何が機能するかを確認する必要があります。

明示的なデータキャッシングには、pg_prewarm拡張を使用することもできます。



2
Ubuntuのページ10.1で大規模な(約6億行のテーブル)でインデックススキャンを機能させるために、random_page_cost = 0.1を設定する必要さえありました。微調整を行わなかった場合、seqスキャンは(並列であるにもかかわらず)12分かかりました(Analyze tableが実行されたことに注意してください!)。ドライブはSSDです。調整後、実行時間は1秒になりました。
Anatoly Alekseev

あなたは私の日を救った。両端で分析を実行した後でも、同じデータベースでまったく同じクエリが1台のマシンで30秒かかり、別のマシンでは1秒未満である方法を理解しようとすると、夢中になりました... ALTER SYSTEM SET random_page_cost = x 'は、新しいデフォルト値をグローバルに設定します。
ジュリアン

10

質問自体は非常に無効です。強制する(たとえば、enable_seqscan = offを実行する)ことは非常に悪い考えです。より高速になるかどうかを確認することは有用かもしれませんが、プロダクションコードはそのようなトリックを使用すべきではありません。

代わりに、クエリの分析を説明し、それを読んで、PostgreSQLが(あなたの意見では)悪い計画を選択する理由を見つけてください。

ウェブ上には、explain analyze出力の読み取りに役立つツールがあります。そのうちの1つは、私が記述したExplain.depesz.comです。

もう1つのオプションは、freenode ircネットワークの#postgresqlチャネルに参加し、そこで助けを求めるためにそこにいる人たちと話し合うことです-クエリを最適化することは「質問して、満足してもらう」の問題ではないためです。それは会話のようなものであり、チェックすべき多くのこと、学ぶべき多くのことがあります。


2

OFFSET 0サブクエリにseqscanを追加することを好むようにpostgresをプッシュするトリックがあります

これは、必要なのが最初/最後のn個の要素のみである場合に、大きなテーブルや巨大なテーブルをリンクするリクエストを最適化するのに便利です。

100k(またはそれ以上)のエントリを持つ複数のテーブルを含む最初/最後の20要素を探しているとします。エントリ。たとえば、このシナリオでは、順次スキャンを実行すると、10倍以上速くなることがわかります。

Postgresがサブクエリをインライン化しないようにするにどうすればよいですか?


素敵なトリック。もちろん、優れたオプティマイザはオフセット0を最適化する必要があります:-)
Guido Leenders '19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.