web-dev-qa-db-ja.com

PostgreSQL:WHERE句のANY(VALUES(...))が原因で大幅な速度低下

さて、私は 以前は 大規模なデータセットに関する質問をしましたが、それは答えられなかったので、それを削減し、以前のセットアップの小さなサブセットについて質問し、私が達成しようとしていることを簡素化することにしました新しい質問-これが少し明確になることを願っています。

1つの大きなテーブル(_report_drugs_)があり、これはディスク上で1775 MBで、3300万行をわずかに含みます。テーブルのレイアウト:

_    Column     |            Type             | Modifiers 
---------------+-----------------------------+-----------
 rid           | integer                     | not null
 drug          | integer                     | not null
 created       | timestamp without time zone | 
 reason        | text                        | 
 duration      | integer                     | 
 drugseq       | integer                     | 
 effectiveness | integer                     | 
Indexes:
  "report_drugs_drug_idx" btree (drug) CLUSTER
  "report_drugs_drug_rid_idx" btree (drug, rid)
  "report_drugs_reason_idx" btree (reason)
  "report_drugs_reason_rid_idx" btree (reason, rid)
  "report_drugs_rid_idx" btree (rid)
_

ご覧のとおり、いくつかのインデックス(すべてがこの質問に関連しているわけではありません)があり、スコープがCLUSTERでテーブルをdrugedしました。このテーブルも、メトリックを取得する前に、自動で手動で_VACUUM ANALYZE_ dになっています。

しかし、次のような単純なクエリ:

_SELECT drug, reason FROM report_drugs WHERE drug = ANY(VALUES  (9557), (17848),
(17880), (18223), (18550), (19020), (19084), (19234), (21295), (21742), 
(23085), (26017), (27016), (29317), (33566), (35818), (37394), (39971), 
(41505), (42162), (44000), (45168), (47386), (48848), (51472), (51570), 
(51802), (52489), (52848), (53663), (54591), (55506), (55922), (57209), 
(57671), (59311), (62022), (62532), (63485), (64134), (66236), (67394), 
(67586), (68134), (68934), (70035), (70589), (70896), (73466), (75931), 
(78686), (78985), (79217), (83294), (83619), (84964), (85831), (88330), 
(89998), (90440), (91171), (91698), (91886), (91887), (93219), (93766), 
(94009), (96341), (101475), (104623), (104973), (105216), (105496), 
(106428), (110412), (119567), (121154));
_

完了するまでに7秒以上かかり、次のクエリプランがあります。

_Nested Loop  (cost=1.72..83532.00 rows=24164 width=26) (actual time=0.947..7385.490 rows=264610 loops=1)
->  HashAggregate  (cost=1.16..1.93 rows=77 width=4) (actual time=0.017..0.036 rows=77 loops=1)
     Group Key: "*VALUES*".column1
     ->  Values Scan on "*VALUES*"  (cost=0.00..0.96 rows=77 width=4) (actual time=0.001..0.007 rows=77 loops=1)
->  Index Scan using report_drugs_drug_idx on report_drugs  (cost=0.56..1081.67 rows=314 width=26) (actual time=0.239..95.568 rows=3436 loops=77)
     Index Cond: (drug = "*VALUES*".column1)
Planning time: 7.009 ms
Execution time: 7393.408 ms
_

ANY(VALUES(..))句に追加する値が多いほど、取得速度が遅くなります。このクエリには200を超える値が含まれる場合があり、完了するまでに30秒以上かかります。それでも、ほんの少しの値(4例)を含めると、200ミリ秒未満でクエリが得られます。したがって、このスローダウンを引き起こしているのは明らかにWHERE句のこの部分です。

このクエリのパフォーマンスを向上させるにはどうすればよいですか?ここで私が見逃している明らかなポイントは何ですか?

ハードウェアとデータベースの設定:

SSDドライブからクラスターを実行しています。システムの総メモリは24 GBで、Debianで実行され、4Ghz 8コアi7-4790プロセッサーを使用します。この種のデータセットには十分なハードウェアが必要です。

重要な_postgresql.conf_の読み出し:

  • shared_buffers = 4GB
  • work_mem = 100 MB
  • checkpoint_completion_target = 0.9
  • 自動バキューム=オン

これに対する副次的な質問:

以前はWHERE drug = ANY(ARRAY[..])を使用していましたが、WHERE drug = ANY(VALUES(..))を使用すると速度が大幅に向上することがわかりました。なぜそれが違いを生むのでしょうか?


編集1-WHERE句の代わりにVALUESに結合

a_horse_with_no_nameがコメントで指摘したように、WHERE句を削除して、ドラッグ値にJOINを使用してクエリを実行しようとしました:

クエリ:

_SELECT drug, reason FROM report_drugs d JOIN (VALUES  (9557), (17848),
(17880), (18223), (18550), (19020), (19084), (19234), (21295), (21742), 
(23085), (26017), (27016), (29317), (33566), (35818), (37394), (39971), 
(41505), (42162), (44000), (45168), (47386), (48848), (51472), (51570), 
(51802), (52489), (52848), (53663), (54591), (55506), (55922), (57209), 
(57671), (59311), (62022), (62532), (63485), (64134), (66236), (67394), 
(67586), (68134), (68934), (70035), (70589), (70896), (73466), (75931), 
(78686), (78985), (79217), (83294), (83619), (84964), (85831), (88330), 
(89998), (90440), (91171), (91698), (91886), (91887), (93219), (93766), 
(94009), (96341), (101475), (104623), (104973), (105216), (105496), 
(106428), (110412), (119567), (121154)) as x(d) on x.d = d.drug;
_

計画(analyzeおよびbuffersjjanesの要求に応じて):

_Nested Loop  (cost=0.56..83531.04 rows=24164 width=26) (actual time=1.003..6927.080 rows=264610 loops=1)
  Buffers: shared hit=12514 read=111251
  ->  Values Scan on "*VALUES*"  (cost=0.00..0.96 rows=77 width=4) (actual time=0.000..0.059 rows=77 loops=1)
  ->  Index Scan using report_drugs_drug_idx on report_drugs d  (cost=0.56..1081.67 rows=314 width=26) (actual time=0.217..89.551 rows=3436 loops=77)
        Index Cond: (drug = "*VALUES*".column1)
        Buffers: shared hit=12514 read=111251
Planning time: 7.616 ms
Execution time: 6936.466 ms
_

ただし、これは効果がないようです。クエリプランは少し変更されましたが、実行時間はほぼ同じで、クエリはまだ低速です。


編集2-VALUESのJOINではなく一時テーブルのJOIN

Lennartのアドバイスに従って、単一のトランザクション内に一時テーブルを作成し、それを薬剤値で埋め、それに対して結合しようとしました。約2秒は得られますが、クエリは5秒弱と非常に遅くなります。

クエリプランが_nested loop_から_hash join_に変更され、現在_sequential scan_テーブルで_report_drugs_を実行しています。これはどういうわけかインデックスが欠落している可能性がありますか(_report_drugs_テーブルのdrug列にはインデックスがあります...)?

_Hash Join  (cost=67.38..693627.71 rows=800224 width=26) (actual time=0.711..4999.222 rows=264610 loops=1)
  Hash Cond: (d.drug = t.drug)
  ->  Seq Scan on report_drugs d  (cost=0.00..560537.16 rows=33338916 width=26) (actual time=0.410..3144.117 rows=33338915 loops=1)
  ->  Hash  (cost=35.50..35.50 rows=2550 width=4) (actual time=0.012..0.012 rows=77 loops=1)
      Buckets: 4096  Batches: 1  Memory Usage: 35kB
      ->  Seq Scan on t  (cost=0.00..35.50 rows=2550 width=4) (actual time=0.002..0.005 rows=77 loops=1)
Planning time: 7.030 ms
Execution time: 5005.621 ms
_
4
Timusan

値のリストと比較するだけです。この場合、INを使用する方が簡単です。

SELECT drug, reason FROM drugs WHERE drug IN (9557,17848,17880,18223,18550);

または、ANYを引き続き使用する場合、配列リテラルを使用すると、INと同じクエリプランになります。

SELECT drug, reason FROM drugs WHERE drug = ANY ('{9557,17848,17880,18223,18550}');

私はこれをより小さいテストテーブルで試してみました。Postgresは、配列リテラルでINおよびANYを使用するクエリのバージョンのインデックススキャンを実行できましたが、VALUESでANYを使用するクエリは実行できませんでした。

結果の計画は次のようになります(ただし、テストテーブルとデータは多少異なります)。

Index Scan using test_data_id_idx on test_data  (cost=0.43..57.43 rows=12 width=8) (actual time=0.014..0.028 rows=12 loops=1)
  Index Cond: (id = ANY ('{1,2,3,4,5,6,7,8,9,10,11,12}'::integer[]))

これは、インデックスを1回スキャンするため、表示したクエリプランよりもはるかに高速ですが、プランはそのWHERE句にドラッグがあると何度もループします。

2
Mad Scientist

結合を使用して書き直してみましたか?何かのようなもの:

SELECT d.drug, d.reason 
FROM drugs d
JOIN (VALUES  (9557), (17848), (17880), (18223), (18550), (19020)
            , (19084), (19234), (21295), (21742), (23085), (26017)
            , ... ) as T(drug)
    ON d.drug = T.drug;

補足として、一部のインデックスは冗長であるように見えます。

編集:一時テーブルを使用

また、仮想テーブルの代わりに一時テーブルを使用することもできます。トランザクションで以下を実行します。

CREATE TABLE T (drug int not null primary key) ON COMMIT DROP;
INSERT INTO T(drug)
VALUES (9557), (17848), (17880), (18223), (18550), ...;

SELECT d.drug, d.reason 
FROM drugs d
JOIN T
    ON d.drug = T.drug;
1
Lennart