J'ai une requête relativement simple sur une table avec 1,5 M de lignes:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
production:
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
Jusqu'ici tout va bien, rapide et utilise les index disponibles.
Maintenant, si je modifie un peu une requête, le résultat sera:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
La EXPLAIN ANALYZE
sortie est:
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
Pas si rapide, et en utilisant seq scan ...
Bien sûr, la requête d'origine exécutée par l'application est un peu plus complexe, et même plus lente, et bien sûr l'original généré en hibernation ne l'est pas (SELECT 9762715)
, mais la lenteur est là même pour ça (SELECT 9762715)
! La requête est générée par hibernate, il est donc très difficile de les modifier, et certaines fonctionnalités ne sont pas disponibles (par exemple, UNION
ne sont pas disponibles, ce qui serait rapide).
Questions
- Pourquoi l'index ne peut-il pas être utilisé dans le deuxième cas? Comment pourraient-ils être utilisés?
- Puis-je améliorer les performances des requêtes d'une autre manière?
Réflexions supplémentaires
Il semble que nous pourrions utiliser le premier cas en effectuant manuellement un SELECT, puis en plaçant la liste résultante dans la requête. Même avec 5000 numéros dans la liste IN (), c'est quatre fois plus rapide que la deuxième solution. Cependant, cela semble juste FAUX (aussi, cela pourrait être 100 fois plus rapide :)). Il est totalement incompréhensible que le planificateur de requêtes utilise une méthode complètement différente pour ces deux requêtes, donc je voudrais trouver une meilleure solution à ce problème.
JOIN
au lieu duIN ()
? En outre, apublication
été analysé récemment?(SELECT 9762715)
.(SELECT 9762715)
. À la question d'hibernation: cela pourrait être fait, mais nécessite une réécriture sérieuse du code, car nous avons des requêtes de critères d'hibernation définies par l'utilisateur qui sont traduites à la volée. Donc, essentiellement, nous modifierions Hibernate, ce qui est une énorme entreprise avec beaucoup d'effets secondaires possibles.Réponses:
Le cœur du problème devient évident ici:
Postgres estime renvoyer 744661 lignes alors qu'en fait, il s'agit d'une seule ligne. Si Postgres ne sait pas mieux à quoi s'attendre de la requête, il ne peut pas mieux planifier. Nous aurions besoin de voir la requête réelle cachée derrière
(SELECT 9762715)
- et probablement aussi de connaître la définition de la table, les contraintes, les cardinalités et la distribution des données. De toute évidence, Postgres ne peut prédire comment quelques lignes sont renvoyées par elle. Il peut y avoir des façons de réécrire la requête, selon ce qu'elle est .Si vous savez que la sous-requête ne peut jamais renvoyer plus de
n
lignes, vous pouvez simplement dire à Postgres en utilisant:Si n est suffisamment petit, Postgres basculera vers les analyses d'index (bitmap). Cependant , cela ne fonctionne que pour le cas simple. Arrête de fonctionner lors de l'ajout d'une
OR
condition: le planificateur de requêtes ne peut actuellement pas y faire face.J'utilise rarement
IN (SELECT ...)
pour commencer. En règle générale, il existe un meilleur moyen de l'implémenter, souvent avec uneEXISTS
semi-jointure. Parfois avec un (LEFT
)JOIN
(LATERAL
) ...La solution de contournement évidente serait d'utiliser
UNION
, mais vous avez exclu cela. Je ne peux pas en dire plus sans connaître la sous-requête réelle et d'autres détails pertinents.la source
(SELECT 9762715)
! Si je lance cette requête exacte que vous voyez ci-dessus. Bien sûr, la requête d'hibernation d'origine est un peu plus compliquée, mais j'ai (je pense) réussi à déterminer où le planificateur de requêtes s'égare, j'ai donc présenté cette partie de la requête. Cependant, les explications ci-dessus et les requêtes sont textuellement ctrl-cv.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
également un balayage séquentiel et dure également environ 3 secondes ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. Et l'effet était toujours là pour les mêmes requêtestest
: n'importe quelle sous-requête résultait d'un scan séquentiel ... J'ai essayé les versions 9.1 et 9.4. L'effet est le même.OR
condition. L'astuce avecLIMIT
ne fonctionne que pour le cas le plus simple.Mon collègue a trouvé un moyen de modifier la requête afin qu'elle ait besoin d'une simple réécriture et fasse ce qu'elle doit faire, c'est-à-dire faire la sous-sélection en une seule étape, puis effectuer les autres opérations sur le résultat:
L'analyse d'expliquer est maintenant:
Il semble que nous pouvons créer un analyseur simple qui recherche et réécrit toutes les sous-sélections de cette façon, et l'ajoutons à un hook de mise en veille prolongée pour manipuler la requête native.
la source
SELECT
s, comme vous l'avez fait dans votre première requête dans la question?SELECT
séparément, puis faire la sélection externe avec une liste statique après leIN
. Cependant, cela est beaucoup plus lent (5 à 10 fois si la sous-requête a plus de quelques résultats), car vous avez des allers-retours réseau supplémentaires et vous avez un format postgres beaucoup de résultats, puis une analyse java de ces résultats (puis faire la même chose à l'envers). La solution ci-dessus fait de même sémantiquement, tout en laissant le processus à l'intérieur des postgres. Dans l'ensemble, cela semble actuellement être le moyen le plus rapide avec la plus petite modification dans notre cas.Réponse à une deuxième question: Oui, vous pouvez ajouter ORDER BY à votre sous-requête, ce qui aura un impact positif. Mais c'est similaire à la solution "EXISTS (sous-requête)" en termes de performances. Il existe une différence significative, même si la sous-requête aboutit à deux lignes.
la source