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 CLUSTER
ed 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
:
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
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.
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;