さて、私は 以前は 大規模なデータセットに関する質問をしましたが、それは答えられなかったので、それを削減し、以前のセットアップの小さなサブセットについて質問し、私が達成しようとしていることを簡素化することにしました新しい質問-これが少し明確になることを願っています。
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
でテーブルをdrug
edしました。このテーブルも、メトリックを取得する前に、自動で手動で_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
_の読み出し:
これに対する副次的な質問:
以前は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
およびbuffers
をjjanesの要求に応じて):
_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
_
値のリストと比較するだけです。この場合、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句にドラッグがあると何度もループします。
結合を使用して書き直してみましたか?何かのようなもの:
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;