web-dev-qa-db-fra.com

PostgreSQL: ANY (VALUES (...)) dans la clause WHERE provoque un ralentissement drastique

D'accord, j'ai précédemment posé une question concernant un grand ensemble de données mais il n'a jamais été répondu, j'ai donc décidé de le réduire et de poser des questions sur un sous-ensemble plus petit de la configuration précédente et de simplifier ce que j'essaie d'accomplir dans une nouvelle question - en espérant que ce sera un peu plus clair.

J'ai une seule grande table (report_drugs) De 1775 Mo sur disque contenant un peu plus de 33 millions de lignes. La disposition de la table:

    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)

Comme vous pouvez le voir, j'ai plusieurs index (pas tous pertinents pour cette question) et j'ai CLUSTERed la table sur l'index de colonne drug car cela est principalement utilisé pour la portée. Le tableau est également VACUUM ANALYZE D automatiquement et manuellement par moi avant d'obtenir des mesures.

Pourtant, une simple requête comme celle-ci:

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));

Prendra plus de 7 secondes et a le plan de requête suivant:

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

Plus j'ajoute de valeurs à ma clause ANY(VALUES(..)), plus elle ralentit. Cette requête peut parfois contenir plus de 200 valeurs, ce qui prendra bien plus de 30 secondes pour se terminer. Pourtant, inclure seulement quelques valeurs (4 ex.) Me donne une requête en moins de 200 ms. C'est donc clairement cette partie de la clause WHERE qui est à l'origine de ce ralentissement.

Que puis-je faire pour améliorer les performances de cette requête? Quels sont les points évidents qui me manquent ici?

Mes paramètres matériels et de base de données:

J'exécute le cluster à partir d'un lecteur SSD. Le système a une mémoire totale de 24 Go, fonctionne sur Debian et utilise un processeur i7-4790 4 cœurs 4Ghz 8. Ce matériel devrait être suffisant pour ce type de jeu de données.

Quelques lectures importantes de postgresql.conf:

  • shared_buffers = 4 Go
  • work_mem = 100 Mo
  • checkpoint_completion_target = 0.9
  • autovacuum = on

Une question secondaire à ceci:

Auparavant, j'avais utilisé WHERE drug = ANY(ARRAY[..]), mais j'ai trouvé que l'utilisation de WHERE drug = ANY(VALUES(..)) me donne une augmentation de vitesse significative. Pourquoi cela devrait-il faire une différence?


Édition 1 - JOIN on VALUES au lieu de la clause WHERE

Comme a_horse_with_no_name l'a souligné dans les commentaires, j'ai essayé de supprimer la clause WHERE et d'effectuer la requête en utilisant un JOIN sur les valeurs du médicament:

Requete:

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;

Plan (avec analyze et buffers comme demandé par 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

Cependant, cela semble n'avoir aucun effet. Bien que le plan de requête ait légèrement changé, le temps d'exécution est à peu près le même et la requête est toujours lente.


Edit 2 - JOIN sur table temporaire au lieu de JOIN sur VALUES

Suivant Lennart 's conseille J'ai essayé de créer une table temporaire dans une seule transaction, en la remplissant avec les valeurs du médicament et en se joignant à cela. Bien que je gagne ~ 2 secondes, la requête est toujours très lente à un peu plus de 5 secondes.

Le plan de requête est passé d'un nested loop À un hash join Et il effectue maintenant un sequential scan Sur la table report_drugs. Serait-ce un index manquant (la colonne drug dans la table report_drugs A un index ...)?

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

Vous comparez simplement avec une liste de valeurs, utiliser IN serait plus simple dans ce cas:

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

Alternativement, si vous utilisez toujours N'IMPORTE QUEL, l'utilisation d'un résultat littéral de tableau dans le même plan de requête que IN pour moi:

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

J'ai essayé c'est une table de test plus petite, et Postgres a pu faire un scan d'index pour la version de la requête en utilisant IN et ANY avec un littéral de tableau, mais pas pour la requête en utilisant ANY avec VALUES.

Le plan résultant est quelque chose comme ceci (mais ma table de test et mes données sont quelque peu différentes):

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[]))

Cela devrait être beaucoup plus rapide que le plan de requête que vous avez montré car il analyse l'index une fois, tandis que votre plan boucle autant de fois que vous avez de médicaments dans cette clause WHERE.

2
Mad Scientist

Avez-vous essayé de réécrire à l'aide d'une jointure? Quelque chose comme:

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;

Par ailleurs, certains de vos index semblent redondants.

Modifier: utiliser la table temporaire

Vous pouvez également essayer d'utiliser une table temporaire au lieu d'une table virtuelle. Dans une transaction:

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