サブクエリを追加するとPostgreSQLクエリが非常に遅くなる


10

150万行のテーブルに対する比較的単純なクエリがあります。

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE 出力:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms

これまでのところ、高速で、使用可能なインデックスを使用しています。
ここで、クエリを少しだけ変更すると、結果は次のようになります。

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE出力は次のようになります。

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms

それほど速くなく、seqスキャンを使用しています...

もちろん、アプリケーションによって実行される元のクエリは少し複雑で、さらに遅くなります。もちろん、休止状態で生成された元のクエリはそう(SELECT 9762715)ではありませんが、それでも速度は低下しません(SELECT 9762715)。クエリはhibernateによって生成されるため、それらを変更することは非常に困難であり、一部の機能は使用できません(たとえばUNION、使用できないため、高速になります)。

質問

  1. 2番目のケースでインデックスを使用できないのはなぜですか?それらはどのように使用できますか?
  2. 他の方法でクエリのパフォーマンスを改善できますか?

追加の考え

最初のケースは手動でSELECTを実行し、結果のリストをクエリに入れることで使用できるようです。IN()リストに5000の数値があっても、2番目のソリューションより4倍高速です。しかし、それは間違っているように見えます(また、100倍も高速になる可能性があります:))。クエリプランナーがこれら2つのクエリに対してまったく異なる方法を使用する理由は完全に理解できないため、この問題に対するより良い解決策を見つけたいと思います。


hibernateがのJOIN代わりにを生成するように、どういうわけかコードを書き換えることができますIN ()か?また、publication最近分析されましたか?
dezso 2015

はい、VACUUM ANALYZEとVACUUM FULLの両方を行いました。パフォーマンスに変化はありませんでした。2番目については、AFAIRを使用してみましたが、クエリのパフォーマンスに大きな影響はありませんでした。
P.Péter

1
Hibernateが適切なクエリの生成に失敗した場合、生のSQLを使用しないのはなぜですか?これは、Googleで翻訳を主張するようなものです。あなたの質問について:それは本当に隠された実際のクエリに依存します(SELECT 9762715)
Erwin Brandstetter、2015

後で述べたように、内部クエリがで あっても遅くなります(SELECT 9762715)。休止状態の質問:実行できますが、オンザフライで翻訳されるユーザー定義の休止状態基準クエリがあるため、深刻なコードの書き換えが必要です。したがって、本質的には、休止状態を変更することになります。これは、多くの可能な副作用を伴う巨大な事業です。
P.Péter

回答:


6

ここで問題の核心が明らかになります。

パブリケーションのシーケンススキャン(コスト= 0.01..349652.84 行= 744661幅= 8)(実際の時間= 2735.888..2841.393 行= 1ループ= 1)

Postgres 744661行を返すと推定していますが、実際には1行であることが判明しています。Postgresがクエリから何を期待できるかがよくわからない場合、より適切に計画することはできません。実際のクエリが背後に隠れていることを確認する必要があります。(SELECT 9762715)また、おそらくテーブルの定義、制約、カーディナリティ、およびデータの分布についても知っている必要があります。明らかに、Postgresが返す行を予測することはできません。それは何に応じて、問合せをリライトする方法があるかもしれませんです

サブクエリが行以上を返すことができないことがわかっている場合はn、次のコマンドを使用してPostgresに通知できます。

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

nが十分に小さい場合、Postgresは(ビットマップ)インデックススキャンに切り替えます。ただし、これは単純な場合にのみ機能します。OR条件を追加すると動作が停止します。現在、クエリプランナーはこれに対応できません。

IN (SELECT ...)そもそもめったに使用しません。通常、同じことを実装するより良い方法があり、多くの場合、EXISTS準結合を使用します。ときどき(LEFTJOINLATERAL)...

明白な回避策はを使用するUNIONことですが、あなたはそれを除外しました。実際のサブクエリやその他の関連する詳細を知らなければ、これ以上言うことはできません。


2
背後に隠されたクエリ はありません(SELECT 9762715)!上記の正確なクエリを実行すると、もちろん、元の休止クエリはもう少し複雑ですが、私は(私が思うに)クエリプランナーがどこに行き着くのかを特定できたので、クエリのその部分を示しました。ただし、上記の説明とクエリはそのままのctrl-cvです。
P.Péter

第二部のように、内側制限は仕事はしません。EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;また、また、シーケンシャルスキャンとは、約3秒...のために走るん
P.Péter

@P.Péter:私のローカルテストでは、Postgres 9.4の実際のサブクエリを使用して動作します。表示されているものが実際のクエリである場合は、既に解決策があります。質問の最初のクエリをサブクエリではなく定数で使用します。
Erwin Brandstetter、2015

まあ、私は新しいテストテーブルでサブクエリも試しました:CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;。そして、同じクエリに対しても影響はありました。test:サブクエリはシーケンススキャンで発生しました... 9.1と9.4の両方を試しました。効果は同じです。
P.Péter

1
@P.Péter:私はテストを再度実行し、OR条件なしでテストしたことに気付きました。のトリックLIMITは、より単純なケースでのみ機能します。
Erwin Brandstetter

6

私の同僚は、クエリを変更して単純な書き換えを必要とし、必要な処理を実行する方法を見つけました。

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

説明分析は今です:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

この方法ですべての副選択を見つけて書き換える単純なパーサーを作成し、それをhibernateフックに追加してネイティブクエリを操作できるようです。


楽しそうですね。SELECT質問の最初のクエリのように、すべてのを削除する方が簡単ではありませんか?
dezso

もちろん、2ステップのアプローチを実行することもできます。SELECT個別に実行してから、の後に静的リストを使用して外部選択を実行しますIN。ただし、追加のネットワークラウンドトリップに加えて、Postgresが多数の結果をフォーマットし、Javaがそれらの結果を解析する(そしてそれから再び同じです)。上記のソリューションは、postgres内にプロセスを残しながら、意味的に同じことを行います。全体として、現在のところ、これは私たちのケースで最小の変更を行う最速の方法のようです。
P.Péter

ああ、分かった。私が知らなかったことは、一度に多くのIDを取得できることです。
dezso

1

2番目の質問への回答:はい、ORDER BYをサブクエリに追加できます。これは、良い影響を与えます。しかし、それはパフォーマンスの「EXISTS(サブクエリ)」ソリューションに似ています。サブクエリの結果が2行になる場合でも、大きな違いがあります。

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.