
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;
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