web-dev-qa-db-fra.com

Les clauses WHERE sont-elles appliquées dans l'ordre où elles sont écrites?

J'essaie d'optimiser une requête qui se penche sur une grande table (37 millions de lignes) et j'ai 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 clauses WHERE pour 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 de grosses boucles pour d'autres clauses, afin de faire une exécution plus rapide?

Maintenant, les requêtes prennent tellement de temps à s'exécuter.

38
Jorge Vega Sánchez

Pour développer la réponse de @ alci:

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

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

  • L'ordre dans lequel les jointures sont écrites est également ignoré jusqu'au join_collapse_limit Configuré; s'il y a plus de jointures que cela, il les exécutera dans l'ordre où elles sont écrites.

  • Les sous-requêtes peuvent être exécutées avant ou après la requête qui les contient, selon ce qui est le plus rapide, tant que la sous-requête est 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 en quelque sorte au milieu, ou entrelacée avec la requête externe.

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

PostgreSQL transformera votre requête

PostgreSQL transformera fortement les requêtes tout en conservant exactement les mêmes effets, afin de les faire fonctionner plus rapidement sans changer les résultats.

  • Les termes en dehors d'une sous-requête peuvent être poussés vers le bas dans la sous-requête afin qu'ils s'exécutent dans le cadre de la sous-requête, pas là où vous les avez écrits dans la requête externe

  • Les termes de la sous-requête peuvent être remontés vers la requête externe afin que leur exécution se fasse dans le cadre de la requête externe, pas là où vous les avez écrits dans la sous-requête

  • La sous-requête peut, et est souvent, aplatie en une jointure sur la table externe. Il en va de même pour des choses comme les requêtes EXISTS et NOT EXISTS.

  • Les vues sont aplaties dans la requête qui utilise la vue

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

  • ... et il existe de nombreuses autres transformations apportées aux requêtes, telles que la pré-évaluation d'expression constante, la décorrélation de certaines sous-requêtes et toutes sortes d'autres astuces de planificateur/optimiseur.

En général, PostgreSQL peut transformer et réécrire massivement votre requête, 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
);

produira généralement tous exactement le même plan de requête. (En supposant que je n'ai pas fait d'erreurs stupides dans ce qui précède de toute façon).

Il n'est pas rare d'essayer d'optimiser une requête uniquement pour découvrir que le planificateur de requêtes a déjà compris les astuces que vous essayez et les a appliquées automatiquement, de sorte que la version optimisée à la main n'est pas meilleure que l'original.

Limites

Le planificateur/optimiseur est loin d'être omniprésent et est limité par l'exigence 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 qui ont été implémentées et le temps CPU 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 d'échantillonnage, surtout si un échantillon trop petit est prélevé. De mauvais choix de plan peuvent en résulter.

  • Les statistiques ne gardent pas trace de certains types de données sur la table, comme 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_cost Pour lui indiquer la vitesse relative des différentes opérations sur le système particulier sur lequel il est installé. Ce ne sont que des guides. S'ils se trompent gravement, ils peuvent conduire à de mauvais choix de plan.

  • Toute sous-requête avec LIMIT ou OFFSET ne peut pas être aplatie ou soumise à un pullup/pushdown. Cela ne signifie cependant pas qu'il s'exécutera avant toutes les parties de la requête externe, ni même qu'il s'exécutera .

  • Les termes CTE (les clauses d'une requête WITH) 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 poussés vers le haut ou vers le bas à travers la barrière des termes CTE. Les termes CTE sont toujours exécutés avant la requête finale. Il s'agit d'un comportement non standard SQL , mais il est documenté comme la façon dont PostgreSQL fait les choses.

  • PostgreSQL a une capacité limitée d'optimiser les requêtes sur les tables étrangères, les vues security_barrier Et certains autres types spéciaux de relations

  • PostgreSQL n'insérera pas une fonction écrite en autre chose que du SQL simple, pas plus que le pullup/pushdown entre elle et la requête externe.

  • Le planificateur/optimiseur est vraiment stupide quant à la sélection d'index d'expression et aux différences triviales de type de données entre l'index et l'expression.

Des tonnes de 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 aplati en une requête plus simple avec un ensemble supplémentaire de jointures, et ce sera très probablement le cas.

Il en résultera 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 optimisera ensuite l'ordre de jointure et les méthodes de jointure en fonction de ses estimations de sélectivité et de nombre de lignes et des index disponibles. Si ceux-ci reflètent raisonnablement la réalité, il fera les jointures et exécutera les entrées de clause where dans l'ordre le plus approprié - souvent en les mélangeant, donc il fait un peu de ceci, puis un peu de cela, puis revient à la première partie , 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'arbre 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écortiquer" ce plan de requête ou l'arborescence de plan interne vers SQL.

http://explain.depesz.com/ a un assistant de plan de requête décent. Si vous êtes totalement novice en matière de plans de requête, etc. (auquel cas je suis étonné que vous soyez parvenu aussi loin dans cet article), PgAdmin dispose d'un visualiseur de plan de requête graphique qui fournit beaucoup moins d'informations mais est plus simple.

Lecture connexe:

Capacités de pushdown/pullup et d'aplatissement continuer à s'améliorer dans chaque version . PostgreSQL est généralement juste sur les décisions de pull-up/push-down/flattening, mais pas toujours, donc parfois vous devez (ab) utiliser un CTE ou le hack OFFSET 0. Si vous trouvez un tel cas, signalez un bogue du planificateur de requêtes.


Si vous êtes vraiment, vraiment désireux, vous pouvez également utiliser l'option debug_print_plans Pour voir le plan de requête brut, mais je vous promets que vous ne voulez pas le lire. Vraiment.

72
Craig Ringer

SQL est un langage déclaratif: vous dites ce que vous voulez, pas comment le faire. Le SGBDR choisira la façon dont il exécutera la requête, appelée 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 de coûts pour la planification. Autrement dit, 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 DB fait de mauvais choix, ce qui entraîne des requêtes très lentes.

17
alci