web-dev-qa-db-ja.com

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

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倍高速です。ただし、それは[〜#〜] wrong [〜#〜]のようです(また、100倍高速になる可能性もあります:))。クエリプランナーがこれら2つのクエリに対してまったく異なる方法を使用する理由は完全に理解できないため、この問題に対するより良い解決策を見つけたいと思います。

9
P.Péter

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

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フックに追加してネイティブクエリを操作できるようです。

6
P.Péter

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

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

Postgresは744661行を返すと見積もるが、実際には1行であることがわかります。 Postgresがクエリから何を期待できるかをよく理解していない場合、より適切に計画することはできません。実際のクエリが_(SELECT 9762715)_の背後に隠れていることを確認する必要があります。また、おそらくテーブルの定義、制約、カーディナリティ、およびデータの分布も知っている必要があります。もちろん、Postgresはfew行がどのように返されるかを予測することはできません。クエリの内容によっては、クエリを書き換える方法がある場合があります。

knowの場合、サブクエリは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を使用することですが、あなたはそれを除外しました。実際のサブクエリやその他の関連する詳細を知らなければ、これ以上は言えません。

5

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

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
1
iki