Les clauses WHERE sont-elles appliquées dans l'ordre dans lequel elles ont été écrites?


36

J'essaie d'optimiser une requête qui examine une grande table (37 millions de lignes) et pose une question sur l'ordre dans lequel les opérations sont exécutées dans une requête.

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

Les WHEREclauses de la plage de dates sont-elles exécutées avant la sous-requête? Est-ce un bon moyen de mettre les clauses les plus restrictives en premier pour éviter les grandes boucles pour les autres clauses, afin de permettre une exécution plus rapide?

Les requêtes prennent maintenant beaucoup de temps à exécuter.

Réponses:


68

Pour élaborer sur la réponse de @ alci:

PostgreSQL ne se soucie pas de l'ordre dans lequel vous écrivez

  • PostgreSQL ne se soucie pas du tout de l'ordre des entrées dans une WHEREclause et choisit les index et l'ordre d'exécution en fonction de l'estimation du coût et de la sélectivité uniquement.

  • L'ordre dans lequel les jointures sont écrites est également ignoré jusqu'à la configuration join_collapse_limit; s'il y a plus de jointures que cela, elles seront exécutées dans l'ordre dans lequel elles ont été écrites.

  • Les sous-requêtes peuvent être exécutées avant ou après la requête qui les contient, en fonction de ce qui est le plus rapide, à condition que la sous-requête soit exécutée avant que la requête externe ait réellement besoin des informations. Souvent, en réalité, la sous-requête est exécutée au milieu ou est entrelacée avec la requête externe.

  • Il n'y a aucune garantie que PostgreSQL exécute réellement une partie de la requête. Ils peuvent être complètement optimisés. Ceci est important si vous appelez des fonctions avec des effets secondaires.

PostgreSQL va transformer votre requête

PostgreSQL va considérablement transformer les requêtes tout en conservant exactement les mêmes effets, afin de les rendre plus rapides sans modifier les résultats.

  • Les termes extérieurs à une sous-requête peuvent être insérés dans la sous-requête afin qu'ils s'exécutent dans le cadre de la sous-requête et non à l'endroit où vous les avez écrits dans la requête externe.

  • Les termes de la sous-requête peuvent être extraits de la requête externe afin que leur exécution soit effectuée dans le cadre de la requête externe et non à l'endroit où vous les avez écrits dans la sous-requête.

  • La sous-requête peut, et est souvent, aplatie dans une jointure sur la table externe. La même chose est vraie des choses comme EXISTSet des NOT EXISTSrequêtes.

  • Les vues sont aplaties dans la requête utilisant la vue

  • Les fonctions SQL sont souvent intégrées à la requête appelante

  • ... et de nombreuses autres transformations ont été apportées aux requêtes, telles que la pré-évaluation de l'expression constante, la décorrélation de certaines sous-requêtes et toutes sortes d'autres astuces de planification / optimisation.

En général, PostgreSQL peut transformer et réécrire votre requête de manière massive, au point où chacune de ces requêtes:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

Généralement, tous produiront exactement le même plan de requête. (En supposant que je n’ai fait aucune erreur stupide dans ce qui précède de toute façon).

Il n'est pas rare d'essayer d'optimiser une requête uniquement pour constater que le planificateur de requêtes a déjà compris les astuces que vous tentez et les a appliquées automatiquement. La version optimisée à la main n'est donc pas meilleure que la version d'origine.

Limites

Le planificateur / optimiseur est loin d'être omniprésent et est limité par l'obligation d'être absolument certain qu'il ne peut pas modifier les effets de la requête, les données disponibles pour prendre des décisions, les règles mises en œuvre et le temps de calcul il peut se permettre de réfléchir aux optimisations. Par exemple:

  • Le planificateur s'appuie sur des statistiques conservées par ANALYZE(généralement via autovacuum). Si ceux-ci sont obsolètes, le choix du plan peut être mauvais.

  • Les statistiques ne sont qu'un échantillon, elles peuvent donc être trompeuses en raison des effets de l'échantillonnage, en particulier si un échantillon trop petit est pris. De mauvais choix de régime peuvent en résulter.

  • Les statistiques ne gardent pas trace de certains types de données sur la table, telles que les corrélations entre les colonnes. Cela peut amener le planificateur à prendre de mauvaises décisions lorsqu'il suppose que les choses sont indépendantes alors qu'elles ne le sont pas.

  • Le planificateur s'appuie sur des paramètres de coût, comme random_page_costpour lui indiquer la vitesse relative des différentes opérations sur le système sur lequel il est installé. Ce ne sont que des guides. S'ils ont tort, ils peuvent mener à de mauvais choix de régimes.

  • Toute sous-requête avec un LIMITou OFFSETne peut pas être aplatie ou être sujette à un pull-up / push-down. Cela ne signifie pas qu'il s'exécutera avant toutes les parties de la requête externe, ni même qu'il s'exécutera du tout .

  • Les termes CTE (les clauses d'une WITHrequête) sont toujours exécutés dans leur intégralité, s'ils sont exécutés du tout. Ils ne peuvent pas être aplatis, et les termes ne peuvent pas être augmentés ou abaissés au-delà de la barrière des termes CTE. Les termes CTE sont toujours exécutés avant la requête finale. Ceci est un comportement non standard SQL , mais il est documenté comment PostgreSQL fait les choses.

  • PostgreSQL a une capacité limitée à optimiser des requêtes sur des tables étrangères, des security_barriervues et certains autres types de relations spéciales.

  • PostgreSQL ™ ne met pas en ligne une fonction écrite dans un langage autre que du SQL simple, pas plus qu’un pull-up / pushdown entre celle-ci et la requête externe.

  • Le planificateur / optimiseur est vraiment stupide en ce qui concerne la sélection d'index d'expression et les différences de type de données triviales entre index et expression.

Des tonnes plus, aussi.

Votre requête

Dans le cas de votre requête:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

rien ne l'empêche d'être transformée en une requête plus simple avec un ensemble supplémentaire de jointures et ce sera très probablement le cas.

Cela donnera probablement quelque chose comme (non testé, évidemment):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

PostgreSQL va ensuite optimiser l'ordre et les méthodes de jointure en fonction de ses estimations de la sélectivité et du nombre de lignes et des index disponibles. Si ceux-ci reflètent raisonnablement la réalité, les jointures sont exécutées et les entrées de la clause where sont exécutées dans l'ordre qui leur convient le mieux, en les mélangeant souvent. , etc.

Comment voir ce que l'optimiseur a fait

Vous ne pouvez pas voir le SQL dans lequel PostgreSQL optimise votre requête, car il convertit le SQL en une représentation d'arborescence de requête interne, puis le modifie. Vous pouvez vider le plan de requête et le comparer à d'autres requêtes.

Il n'y a aucun moyen de "déparper" ce plan de requête ou l'arborescence de plan interne en SQL.

http://explain.depesz.com/ dispose d'un assistant de plan de requête décent. Si vous êtes totalement nouveau en matière de plans de requête, etc. (dans ce cas, je suis étonné que vous ayez réussi à le faire jusqu'à présent dans cet article), alors PgAdmin propose un visualiseur graphique de plan de requête qui fournit beaucoup moins d'informations mais est plus simple.

Lecture connexe:

Les capacités de compression et de compression et d'aplatissement continuent de s'améliorer dans chaque version . PostgreSQL a généralement raison sur les décisions d'abaissement / d'abaissement / d'aplatissement, mais pas toujours. Vous devez donc parfois utiliser un CTE ou un OFFSET 0hack. Si vous trouvez un tel cas, signalez un bogue du planificateur de requêtes.


Si vous êtes vraiment très enthousiaste, vous pouvez également utiliser l' debug_print_plansoption pour voir le plan de requête brut, mais je vous promets que vous ne voulez pas lire cela. Vraiment.


Wow ... réponse assez complète :-) L'un des cas où j'avais des plans lents avec Postgresql (ainsi qu'avec d'autres moteurs de base de données bien connus, comme Oracle), concerne les corrélations entre colonnes ou plusieurs jointures corrélées. Il finira souvent par faire des boucles imbriquées, pensant qu'il n'y a que quelques lignes à ce stade du plan, alors qu'il y en a plusieurs milliers. Une façon d'optimiser ce type de requête consiste à 'définir enable_nestloop = off;' pour la durée de la requête.
alci

J'ai rencontré une situation où la v9.5.5 essayait d'appliquer TO_DATE avant la vérification pour savoir si elle pouvait être appliquée, dans une simple requête de clause 7 where. L'ordre importait.
user1133275

@ user1133275 Dans ce cas, cela n'a fonctionné pour vous que par hasard, car les estimations de coûts de calcul étaient les mêmes. PostgreSQL ™ peut toujours décider de s'exécuter to_dateavant la vérification dans une version ultérieure ou en raison de certaines modifications des statistiques de l'optimiseur. Pour exécuter de manière fiable une vérification avant une fonction qui ne doit être exécutée qu’après cette vérification, utilisez une CASEinstruction.
Craig Ringer

une des plus grandes réponses que j'ai jamais vue sur SO! Bravo, mec!
62mkv

J'ai rencontré des situations où, pour une simple requête, l'ajout order byrendait l'exécution de la requête beaucoup plus rapide que s'il n'y en avait pas order by. C’est l’une des raisons pour lesquelles j’écris mes requêtes avec des jointures de manière à vouloir les exécuter. C’est bien d’avoir un excellent optimiseur, mais je pense qu’il n’est pas sage de se fier entièrement à son destin et d’écrire des requêtes sans se demander comment. il a may beexécuté ... Super réponse !!
Greg0ry

17

SQL est un langage déclaratif: vous dites ce que vous voulez, pas comment le faire. Le SGBDR choisira le mode d'exécution de la requête, appelé plan d'exécution.

Il était une fois (il y a 5 à 10 ans), la façon dont une requête était écrite avait un impact direct sur le plan d'exécution, mais de nos jours, la plupart des moteurs de base de données SQL utilisent un optimiseur basé sur les coûts pour la planification. C'est-à-dire qu'il évaluera différentes stratégies pour exécuter la requête, en fonction de ses statistiques sur les objets de base de données, et choisira la meilleure.

La plupart du temps, c'est vraiment le meilleur, mais parfois, le moteur de base de données fait de mauvais choix, ce qui entraîne des requêtes très lentes.


Il convient de noter que sur certains SGBDR, l'ordre des requêtes est toujours important, mais pour les plus avancés, tout ce que vous dites est vrai, en pratique comme en théorie. Lorsque le planificateur de requêtes sélectionne de mauvais choix d’ordre d’exécution, il existe généralement des astuces permettant de le pousser plus efficacement (comme WITH(INDEX(<index>))dans MSSQL pour forcer le choix de l’index d’une jointure particulière).
David Spillett

La question est de savoir si un index date_dayexiste réellement. S'il n'y en a pas, l'optimiseur n'a pas beaucoup de projets à comparer.
jkavalik
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.