Optimisation des performances des requĂȘtes dans PostgreSQL

Optimisation des performances de la base de donnĂ©es - les dĂ©veloppeurs aiment ou dĂ©testent gĂ©nĂ©ralement cela J'apprĂ©cie cela et souhaite partager certaines des mĂ©thodes que j'ai utilisĂ©es rĂ©cemment pour rĂ©gler les requĂȘtes mal exĂ©cutĂ©es dans PostgreSQL. Mes mĂ©thodes ne sont pas exhaustives, mais plutĂŽt un manuel pour ceux qui se contentent de rĂ©gler.

Recherche de requĂȘtes lentes


La premiÚre façon évidente de commencer le réglage est de trouver des opérateurs spécifiques qui fonctionnent mal.

pg_stats_statements


Le module pg_stats_statements est un excellent point de dĂ©part. Il ne fait que suivre les statistiques d'exĂ©cution des instructions SQL et peut ĂȘtre un moyen facile de trouver des requĂȘtes inefficaces.

Une fois que vous avez installĂ© ce module, une vue systĂšme appelĂ©e pg_stat_statements sera disponible avec toutes ses propriĂ©tĂ©s. Une fois qu'il a la possibilitĂ© de collecter suffisamment de donnĂ©es, recherchez les requĂȘtes qui ont une valeur total_time relativement Ă©levĂ©e . Concentrez-vous d'abord sur ces opĂ©rateurs.

SELECT * FROM pg_stat_statements ORDER BY total_time DESC; 

user_iddbidqueryidinterrogerles appelstotal_time
16384163852948SELECT address_1 FROM adresses a INNER JOIN people p ON a.person_id = p.id WHERE a.state = @state_abbrev;3948315224.670
1638416385924SELECT person_id FROM people WHERE name = name ;2648312225.670
1638416385395SELECT _ FROM commandes WHERE EXISTS (select _ from products where is_featured = true)18583224,67


auto_explain


Le module auto_explain est Ă©galement utile pour trouver des requĂȘtes lentes, mais il prĂ©sente 2 avantages Ă©vidents: il enregistre le plan d'exĂ©cution rĂ©el et prend en charge l'enregistrement des instructions imbriquĂ©es Ă  l'aide de l'option log_nested_statements . Les instructions imbriquĂ©es sont des instructions qui sont exĂ©cutĂ©es Ă  l'intĂ©rieur d'une fonction. Si votre application utilise de nombreuses fonctionnalitĂ©s, auto_explain est inestimable pour obtenir des plans d'exĂ©cution dĂ©taillĂ©s.

L'option log_min_duration contrĂŽle les plans d'exĂ©cution des requĂȘtes qui sont enregistrĂ©s en fonction de leur durĂ©e d'exĂ©cution. Par exemple, si vous dĂ©finissez la valeur sur 1000, tous les enregistrements qui durent plus d'une seconde seront enregistrĂ©s.

Réglage de l'index


Une autre stratégie de réglage importante consiste à s'assurer que les index sont utilisés correctement. Comme condition préalable, nous devons inclure le collecteur de statistiques.

Postgres Statistics Collector est un sous-systĂšme de premiĂšre classe qui collecte toutes sortes de statistiques de performances utiles.

En activant ce collecteur, vous obtenez des tonnes de vues pg_stat _... qui contiennent toutes les propriétés. En particulier, j'ai trouvé cela particuliÚrement utile pour trouver des index manquants et inutilisés.

Index manquants


Les index manquants peuvent ĂȘtre l'une des solutions les plus simples pour amĂ©liorer les performances des requĂȘtes. Cependant, ils ne sont pas une solution miracle et doivent ĂȘtre utilisĂ©s correctement (plus d'informations Ă  ce sujet plus tard). Si le collecteur de statistiques est activĂ©, vous pouvez exĂ©cuter la requĂȘte suivante ( source ).

 SELECT relname, seq_scan - idx_scan AS too_much_seq, CASE WHEN seq_scan - coalesce(idx_scan, 0) > 0 THEN 'Missing Index?' ELSE 'OK' END, pg_relation_size(relname::regclass) AS rel_size, seq_scan, idx_scan FROM pg_stat_all_tables WHERE schemaname = 'public' AND pg_relation_size(relname::regclass) > 80000 ORDER BY too_much_seq DESC; 

La requĂȘte trouve des tables qui ont plus d'analyses sĂ©quentielles (analyses d'index) que d'analyses d'index - une indication claire que l'index aidera. Cela ne vous dira pas sur quelles colonnes crĂ©er l'index, cela prendra donc un peu plus de travail. Cependant, savoir quelles tables en ont besoin est une bonne premiĂšre Ă©tape.

Index inutilisés


Indexer toutes les entitĂ©s, non? Saviez-vous que les index non utilisĂ©s peuvent nuire aux performances d'Ă©criture? La raison en est que lors de la crĂ©ation de l'index Postgres, il est chargĂ© de mettre Ă  jour cet index aprĂšs les opĂ©rations d'Ă©criture (INSERT / UPDATE / DELETE). Ainsi, l'ajout d'un index est un acte d'Ă©quilibrage, car il peut accĂ©lĂ©rer la lecture des donnĂ©es (s'il a Ă©tĂ© créé correctement), mais il ralentira les opĂ©rations d'Ă©criture. Pour rechercher des index inutilisĂ©s, vous pouvez exĂ©cuter la requĂȘte suivante.

 SELECT indexrelid::regclass as index, relid::regclass as table, 'DROP INDEX ' || indexrelid::regclass || ';' as drop_statement FROM pg_stat_user_indexes JOIN pg_index USING (indexrelid) WHERE idx_scan = 0 AND indisunique is false; 

Remarque sur les statistiques de l'environnement de développement


Se fier aux statistiques d'une base de donnĂ©es de dĂ©veloppement local peut ĂȘtre problĂ©matique. IdĂ©alement, vous pouvez obtenir les statistiques ci-dessus Ă  partir de votre machine de travail ou les gĂ©nĂ©rer Ă  partir d'une sauvegarde de travail restaurĂ©e. Pourquoi? Les facteurs environnementaux peuvent modifier le comportement de l'optimiseur de requĂȘte Postgres. Deux exemples:

  • lorsque la machine a moins de mĂ©moire, PostgreSQL peut ne pas ĂȘtre en mesure d'effectuer une jointure par hachage, sinon elle le fera et le fera plus rapidement.
  • s'il n'y a pas autant de lignes dans la table (comme dans la base de donnĂ©es de dĂ©veloppement), PostgresSQL peut prĂ©fĂ©rer effectuer une analyse sĂ©quentielle de la table plutĂŽt que d'utiliser un index disponible. Lorsque les tailles de table sont petites, Seq Scan peut ĂȘtre plus rapide. (Remarque: vous pouvez exĂ©cuter
     SET enable_seqscan = OFF 
    dans une session afin que l'optimiseur choisisse d'utiliser des index, mĂȘme si les analyses sĂ©quentielles peuvent ĂȘtre plus rapides. Ceci est utile lorsque vous travaillez avec des bases de donnĂ©es de dĂ©veloppement qui ne contiennent pas beaucoup de donnĂ©es)

Comprendre les plans d'exécution


Maintenant que vous avez trouvĂ© quelques requĂȘtes lentes, il est temps de commencer Ă  vous amuser.

EXPLIQUER


La commande EXPLAIN est certainement requise lors de la configuration des requĂȘtes. Il vous dit ce qui se passe vraiment. Pour l'utiliser, il suffit d'ajouter EXPLAIN Ă  la requĂȘte et de l'exĂ©cuter. PostgreSQL vous montrera le plan d'exĂ©cution qu'il a utilisĂ©.

Lorsque vous utilisez EXPLAIN pour le réglage, je recommande de toujours utiliser l'option ANALYZE ( EXPLAIN ANALYZE ), car elle vous donne des résultats plus précis. L'option ANALYSER exécute en fait l'instruction (plutÎt que de simplement l'évaluer), puis l'explique.

Prenons un plongeon et commençons à comprendre la sortie d' EXPLAIN . Voici un exemple:



Noeuds


La premiĂšre chose Ă  comprendre est que chaque bloc en retrait avec le prĂ©cĂ©dent «->» (avec la ligne supĂ©rieure) est appelĂ© un nƓud. Un nƓud est une unitĂ© de travail logique (une «étape», si vous le souhaitez) avec un coĂ»t et un dĂ©lai d'exĂ©cution associĂ©s. Le coĂ»t et le temps prĂ©sentĂ©s sur chaque nƓud sont cumulatifs et rassemblent tous les nƓuds enfants. Cela signifie que la ligne la plus haute (nƓud) indique le coĂ»t total et le temps rĂ©el pour l'opĂ©rateur entier. Ceci est important car vous pouvez facilement explorer en avant pour dĂ©terminer quels nƓuds sont le goulot d'Ă©tranglement.

Coût


 cost=146.63..148.65 

Le premier nombre est le coĂ»t initial (le coĂ»t d'obtention du premier enregistrement), et le deuxiĂšme nombre est le coĂ»t de traitement de l'ensemble du nƓud (coĂ»t total du dĂ©but Ă  la fin).

En fait, c'est le coĂ»t que les estimations de PostgreSQL devront ĂȘtre satisfaites pour exĂ©cuter l'instruction. Ce nombre ne signifie pas combien de temps il faudra pour rĂ©pondre Ă  la demande, bien qu'il y ait gĂ©nĂ©ralement une relation directe nĂ©cessaire pour terminer. Le coĂ»t est une combinaison de 5 Ă©lĂ©ments de travail utilisĂ©s pour Ă©valuer le travail requis: Ă©chantillonnage sĂ©quentiel, Ă©chantillonnage incohĂ©rent (alĂ©atoire), traitement en ligne, opĂ©rateur de traitement (fonction) et enregistrement de l'indice de traitement. Le coĂ»t est l'entrĂ©e / sortie et la charge du processeur, et il est important de savoir que le coĂ»t relativement Ă©levĂ© signifie que PostgresSQL pense qu'il devra faire plus de travail. L'optimiseur dĂ©cide du plan d'exĂ©cution Ă  utiliser en fonction du coĂ»t. L'optimiseur prĂ©fĂšre des coĂ»ts infĂ©rieurs.

Heure réelle


 actual time=55.009..55.012 

En millisecondes, le premier nombre est l'heure de dĂ©but (temps pour rĂ©cupĂ©rer le premier enregistrement) et le deuxiĂšme nombre est le temps requis pour traiter le nƓud entier (temps total du dĂ©but Ă  la fin). Facile Ă  comprendre, non?

Dans l'exemple ci-dessus, il a fallu 55,009 ms pour obtenir le premier enregistrement et 55,012 ms pour terminer le nƓud entier.

En savoir plus sur les plans d'exécution.


Il existe de trÚs bons articles pour comprendre les résultats EXPLAIN. Au lieu d'essayer de les raconter ici, je recommande de prendre le temps de vraiment les comprendre en allant vers ces 2 merveilleuses ressources:


Demander un réglage


Maintenant que vous savez quels opĂ©rateurs fonctionnent mal et que vous pouvez voir vos plans d'exĂ©cution, il est temps de commencer Ă  rĂ©gler votre requĂȘte pour amĂ©liorer les performances. Ici, vous modifiez vos requĂȘtes et / ou ajoutez des index pour essayer d'obtenir un meilleur plan d'exĂ©cution. Commencez par les goulots d'Ă©tranglement et voyez s'il y a des changements que vous pouvez apporter pour rĂ©duire les coĂ»ts et / ou les dĂ©lais.

Cache de données et note de coût


Lors des modifications et de l'Ă©valuation des plans d'implĂ©mentation, afin de voir s'il y aura des amĂ©liorations, il est important de savoir que les implĂ©mentations futures peuvent dĂ©pendre de la mise en cache des donnĂ©es qui donne une idĂ©e des meilleurs rĂ©sultats. Si vous exĂ©cutez la demande une fois, apportez une correction et exĂ©cutez-la une deuxiĂšme fois, il est probable qu'elle s'exĂ©cute beaucoup plus rapidement, mĂȘme si le plan d'exĂ©cution n'est pas plus favorable. En effet, PostgreSQL pourrait mettre en cache les donnĂ©es utilisĂ©es au premier dĂ©marrage et peut les utiliser au deuxiĂšme dĂ©marrage. Par consĂ©quent, vous devez effectuer les requĂȘtes au moins 3 fois et faire la moyenne des rĂ©sultats pour comparer les coĂ»ts.

Les choses que j'ai apprises peuvent aider à améliorer les plans d'exécution:

  • Indices
    • Exclure l'analyse sĂ©quentielle (Seq Scan) en ajoutant des index (si la taille de la table n'est pas petite)
    • Lorsque vous utilisez un index multi-colonnes, assurez-vous de faire attention Ă  l'ordre dans lequel vous dĂ©finissez les colonnes incluses - Plus d'informations
    • Essayez des index trĂšs sĂ©lectifs pour les donnĂ©es frĂ©quemment utilisĂ©es. Cela rendra leur utilisation plus efficace.
  • Condition OERE

    • Évitez comme
    • Évitez les appels de fonction dans la clause WHERE
    • Évitez les grosses conditions dans ()
  • JOINS

    • Lorsque vous joignez des tables, essayez d'utiliser une expression d'Ă©galitĂ© simple dans la clause ON (c'est-Ă -dire a.id = b.person_id). Cela vous permet d'utiliser des mĂ©thodes de jointure plus efficaces (c'est-Ă -dire la jointure par hachage, pas la jointure par boucle imbriquĂ©e)
    • Convertissez les sous-requĂȘtes en instructions JOIN lorsque cela est possible, car cela permet gĂ©nĂ©ralement Ă  l'optimiseur de comprendre l'objectif et Ă©ventuellement de choisir le meilleur plan.
    • Utilisez les COMPOSÉS correctement: utilisez-vous GROUP BY ou DISTINCT juste parce que vous obtenez des rĂ©sultats en double? Cela indique gĂ©nĂ©ralement une mauvaise utilisation des JOIN et peut entraĂźner des coĂ»ts plus Ă©levĂ©s.
    • Si le plan d'exĂ©cution utilise Hash Join, il peut ĂȘtre trĂšs lent si les estimations de taille de table sont incorrectes. Par consĂ©quent, assurez-vous que les statistiques de votre table sont exactes en examinant la stratĂ©gie d'aspiration.
    • Évitez autant que possible les sous-requĂȘtes corrĂ©lĂ©es ; ils peuvent augmenter considĂ©rablement le coĂ»t d'une demande
    • Utilisez EXISTS lors de la vĂ©rification de l'existence de chaĂźnes en fonction d'un critĂšre, car il est similaire Ă  un court-circuit (arrĂȘte le traitement lorsqu'il trouve au moins une correspondance)
  • Recommandations gĂ©nĂ©rales

    • Faites plus avec moins; Processeur plus rapide que les entrĂ©es / sorties (E / S)
    • Utilisez les expressions de table communes et les tables temporaires lorsque vous devez effectuer des requĂȘtes chaĂźnĂ©es.
    • Évitez les instructions LOOP et prĂ©fĂ©rez les opĂ©rations SET
    • Évitez COUNT (*) car PostgresSQL analyse les tables pour cela ( uniquement pour les versions <= 9.1 )
    • Dans la mesure du possible, Ă©vitez ORDER BY, DISTINCT, GROUP BY, UNION, car cela entraĂźne des coĂ»ts initiaux Ă©levĂ©s.
    • Recherchez la grande diffĂ©rence entre les lignes estimĂ©es et rĂ©elles dans l'expression EXPLAIN . Si le compteur est trĂšs diffĂ©rent, les statistiques de la table peuvent ĂȘtre obsolĂštes et PostgreSQL estime le coĂ»t en utilisant des statistiques inexactes. Par exemple:
       Limit (cost=282.37..302.01 rows=93 width=22) (actual time=34.35..49.59 rows=2203 loops=1) 
      Le nombre estimé de lignes était de 93, et le réel - 2203. Par conséquent, il s'agit trÚs probablement d'une mauvaise décision du plan. Vous devez revoir votre stratégie d'aspiration et vous assurer que l'ANALYSE est exécutée assez souvent.

Source: https://habr.com/ru/post/fr461997/


All Articles