{"id":10697,"date":"2025-05-13T16:20:05","date_gmt":"2025-05-13T15:20:05","guid":{"rendered":"https:\/\/blog.capdata.fr\/?p=10697"},"modified":"2025-05-13T16:29:21","modified_gmt":"2025-05-13T15:29:21","slug":"postgresql-basics-lire-un-plan-dexecution","status":"publish","type":"post","link":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/","title":{"rendered":"PostgreSQL Basics : lire un plan d&#8217;ex\u00e9cution comme un\u00b7e pro (ou presque)"},"content":{"rendered":"<a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox\" data-provider=\"twitter\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Twitter\" href=\"https:\/\/twitter.com\/intent\/tweet?url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"twitter\" title=\"Share on Twitter\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/twitter.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox\" data-provider=\"linkedin\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Linkedin\" href=\"https:\/\/www.linkedin.com\/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697&#038;title=PostgreSQL%20Basics%20%3A%20lire%20un%20plan%20d%E2%80%99ex%C3%A9cution%20comme%20un%C2%B7e%20pro%20%28ou%20presque%29\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"linkedin\" title=\"Share on Linkedin\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/linkedin.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox\" data-provider=\"mail\" rel=\"nofollow\" title=\"Share by email\" href=\"mailto:?subject=PostgreSQL%20Basics%20%3A%20lire%20un%20plan%20d%E2%80%99ex%C3%A9cution%20comme%20un%C2%B7e%20pro%20%28ou%20presque%29&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"mail\" title=\"Share by email\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/mail.png\" \/><\/a><h1>Introduction :<\/h1>\n<p>Quand une requ\u00eate PostgreSQL est consid\u00e9r\u00e9e comme lente ou que ses performances se d\u00e9gradent soudainement, il y a un r\u00e9flexe \u00e0 toujours avoir : utiliser les plans d&#8217;ex\u00e9cution. Je n\u2019apprends certainement rien \u00e0 la majorit\u00e9 des personnes qui liront cet article, mais si cela permet de garder vigilants les d\u00e9butants et de leur enseigner quelques ficelles, alors cet article vaut le coup.<\/p>\n<p>Si vous avez d\u00e9j\u00e0 \u00e9t\u00e9 dans le cas de figure o\u00f9 un plan d&#8217;ex\u00e9cution est trop gros pour que vous sachiez par quel bout le prendre, ou si vous avez l&#8217;impression qu&#8217;on vous parle chinois quand on \u00e9voque les index scan ou les &#8220;cost&#8221; d&#8217;une requ\u00eate, alors vous \u00eates au bon endroit.<\/p>\n<p>Dans cet article, premier d&#8217;une s\u00e9rie sur les bases de PostgreSQL, nous allons d\u00e9mystifier l&#8217;optimisation des requ\u00eates en utilisant EXPLAIN comme point de d\u00e9part, et les plans d&#8217;ex\u00e9cution g\u00e9n\u00e9r\u00e9s comme fil conducteur.<\/p>\n<p>L\u2019objectif : vous aider \u00e0 identifier rapidement les informations cl\u00e9s, rep\u00e9rer les goulots d\u2019\u00e9tranglement, et gagner en autonomie pour diagnostiquer les performances de vos requ\u00eates.<\/p>\n<p>C\u2019est parti !<\/p>\n<h2>EXPLAIN ANALYZE : c\u2019est quoi exactement et pourquoi l\u2019utiliser ?<\/h2>\n<p>Pour commencer, il faut savoir qu&#8217;un moteur de base de donn\u00e9es, que ce soit PostgreSQL ou un autre ne &#8220;lit&#8221; pas une requ\u00eate comme un humain pourrait la lire, de gauche \u00e0 droite. Il \u00e9labore plut\u00f4t un plan d&#8217;ex\u00e9cution : il cr\u00e9\u00e9 un ensemble d&#8217;\u00e9tape qui se veulent le plus optimis\u00e9es possible pour aller chercher les donn\u00e9es que vous lui demander de la fa\u00e7on la plus efficace possible.<\/p>\n<p>Il y a un moyen simple de voir ce plan d&#8217;ex\u00e9cution, c&#8217;est d&#8217;utiliser la commande EXPLAIN ANALYZE. Elle nous permet en plus de comprendre de quelle fa\u00e7on le moteur \u00e0 r\u00e9ellement ex\u00e9cut\u00e9 notre ordre SQL (et pas seulement comment il pensait le faire). C&#8217;est un peu comme lire un journal de bord de l&#8217;ex\u00e9cution de la requ\u00eate.<\/p>\n<h3>Prenons un exemple simple :<\/h3>\n<p>Imaginons que nous avons une table users, constitu\u00e9e comme tel :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE users (\r\nid integer PRIMARY KEY,\r\nname vachar(30),\r\nage integer,\r\nemail varchar(80) );<\/pre>\n<p>Et que nous souhaitons utiliser la requ\u00eate suivante dans notre application :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT * FROM users WHERE email = 'foo@example.com';<\/pre>\n<p>Sur cette table, nous n&#8217;avons pas d&#8217;index. Voici donc ce \u00e0 quoi pourrait ressembler notre plan d&#8217;\u00e9xecution si nous utilisons la commande :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'foo@example.com';\r\n\r\nSeq Scan on users (cost=0.00..35.50 rows=1 width=100)\r\nFilter: (email = 'foo@example.com')\r\nRows Removed by Filter: 999\r\nActual time=0.010..0.420 rows=1 loops=1\r\n<\/pre>\n<h3>Les termes du plan d&#8217;ex\u00e9cution :<\/h3>\n<p>A premi\u00e8re vue, il n&#8217;est pas \u00e9vident de comprendre tout les termes que l&#8217;on voit dans le plan d&#8217;ex\u00e9cution g\u00e9n\u00e9r\u00e9.<\/p>\n<p>Voici un r\u00e9capitulatif des principaux :<\/p>\n<ul>\n<li><strong>Seq Scan \/ Index Scan :<\/strong> Le type de parcours utilis\u00e9 (s\u00e9quentiel ou via un index)<\/li>\n<li><strong>cost=\u2026 :<\/strong> Estimation du &#8220;co\u00fbt&#8221; total de l\u2019op\u00e9ration, selon PostgreSQL. Le co\u00fbt n&#8217;est pas exprim\u00e9 dans une unit\u00e9 particuli\u00e8re, c&#8217;est juste un indicatif. Plus il est \u00e9lev\u00e9, plus l&#8217;op\u00e9ration est &#8220;co\u00fbteuse&#8221;, notre but \u00e9tant d&#8217;\u00e9viter les co\u00fbts \u00e9normes pour que tout soit plus simple.<\/li>\n<li><strong>rows=\u2026 :<\/strong> Estimation du nombre de lignes que l&#8217;\u00e9tape va retourner<\/li>\n<li><strong>actual time=\u2026 :<\/strong> Le temps r\u00e9el que cette \u00e9tape a pris (en millisecondes)<\/li>\n<li><strong>Rows Removed by Filter :<\/strong> Nombre de lignes lues mais \u00e9limin\u00e9es par un filtre<\/li>\n<li><strong>loops=1 :<\/strong> Nombre de fois que cette \u00e9tape a \u00e9t\u00e9 ex\u00e9cut\u00e9e (ex. dans une boucle)<\/li>\n<\/ul>\n<h3>Puis un exemple plus compliqu\u00e9 :<\/h3>\n<p>Attention, tout les plans d&#8217;ex\u00e9cution de requ\u00eate ne sont pas aussi simples que celui que je viens de vous pr\u00e9senter. Bien souvent ils se pr\u00e9sentent sous la forme de plusieurs n\u0153uds indent\u00e9s\u00a0 qu&#8217;il faut lire dans le bon ordre. Imaginons une nouvelle table orders qui r\u00e9pertorie les commandes pass\u00e9es par un user :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE orders (\r\nid_orders integer PRIMARY KEY,\r\ncreated_at timestamp,\r\ntotal numeric,\r\nstatus text,\r\nuser_id integer references users(id));<\/pre>\n<p>Nous pouvons alors imaginer la requ\u00eate suivante :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT u.name, o.total, o.created_at\r\nFROM users u\r\nJOIN orders o ON u.id = o.user_id\r\nWHERE o.status = 'completed'\r\nORDER BY o.created_at DESC\r\nLIMIT 10;<\/pre>\n<p>Qui remonte les dix premi\u00e8res commandes,\u00a0 avec le nom de l&#8217;acheteur, le total de la commande et sa date, pour les 10 premi\u00e8res commandes dans l&#8217;ordre des dates dont le statut est &#8220;completed&#8221;.<\/p>\n<p>Si on execute un explain analyze sur cette requ\u00eate, on obtient un plan d&#8217;execution de cet ordre :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> Limit (cost=123.45..123.48 rows=10 width=40) (actual time=1.234..1.239 rows=10 loops=1)\r\n   -&amp;gt; Sort (cost=123.45..130.00 rows=2620 width=40) (actual time=1.234..1.236 rows=10 loops=1)\r\n      Sort Key: o.created_at DESC\r\n      Sort Method: top-N heapsort Memory: 25kB\r\n       -&amp;gt; Nested Loop (cost=0.42..100.00 rows=2620 width=40) (actual time=0.045..0.980 rows=200 loops=1)\r\n            -&amp;gt; Index Scan using idx_orders_created_at on orders o (cost=0.29..45.00 rows=2620 width=24) (actual time=0.030..0.340 rows=200 loops=1)\r\n                 Filter: (status = 'completed')\r\n                 Rows Removed by Filter: 50\r\n            -&amp;gt; Index Scan using users_pkey on users u (cost=0.13..0.20 rows=1 width=16) (actual time=0.003..0.004 rows=1 loops=200)\r\n                 Index Cond: (id = o.user_id)<\/pre>\n<p>Si on devait visualiser ce plan d&#8217;ex\u00e9cution de mani\u00e8re graphique pour qu&#8217;il soit plus simple \u00e0 lire, \u00e7a donnerait \u00e7a :<\/p>\n<p><a href=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/chrome_zyhdtutiuX.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-10701\" src=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/chrome_zyhdtutiuX-300x170.png\" alt=\"\" width=\"508\" height=\"288\" srcset=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/chrome_zyhdtutiuX-300x170.png 300w, https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/chrome_zyhdtutiuX-768x434.png 768w, https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/chrome_zyhdtutiuX.png 853w\" sizes=\"auto, (max-width: 508px) 100vw, 508px\" \/><\/a><\/p>\n<h3>Comment lire un plan d&#8217;ex\u00e9cution :<\/h3>\n<p>Pour lire un plan d&#8217;ex\u00e9cution, on part toujours du n\u0153ud le plus indent\u00e9, puis on remonte vers les n\u0153uds sup\u00e9rieur. Dans notre cas, on partirais des deux index scan pour remonter ensuite sur le Nested Loop, puis sur le Sort, et enfin sur le Limit.<br \/>\nLa raison est simple : le plan d\u2019ex\u00e9cution refl\u00e8te l\u2019ordre r\u00e9el d\u2019ex\u00e9cution de la requ\u00eate par PostgreSQL.<br \/>\nChaque \u00e9tape du plan consomme les r\u00e9sultats produits par les \u00e9tapes pr\u00e9c\u00e9dentes. Autrement dit : PostgreSQL commence par les op\u00e9rations de lecture (acc\u00e8s aux tables, scans d\u2019index&#8230;), puis applique les jointures, les filtres, les tris, etc. en remontant vers le haut du plan.<br \/>\nLe n\u0153ud le plus &#8220;haut&#8221; du plan (celui avec le moins d\u2019indentation) correspond \u00e0 l\u2019op\u00e9ration finale, celle qui retourne les r\u00e9sultats \u00e0 l\u2019utilisateur. Les blocs en dessous (plus indent\u00e9s) sont les d\u00e9pendances n\u00e9cessaires pour y arriver.<br \/>\nC\u2019est donc une logique de pipeline de traitement :<\/p>\n<ol>\n<li><strong>Lire les donn\u00e9es<\/strong><\/li>\n<li><strong>Les filtrer<\/strong><\/li>\n<li><strong>Les combiner<\/strong><\/li>\n<li><strong>Les trier \/ agr\u00e9ger<\/strong><\/li>\n<li><strong>Les retourner<\/strong><\/li>\n<\/ol>\n<p>Lire un plan d\u2019ex\u00e9cution &#8220;du plus profond vers le haut&#8221;, c\u2019est suivre le chemin de vie d\u2019une ligne de r\u00e9sultat depuis le disque jusqu\u2019\u00e0 votre terminal.<\/p>\n<h3>Petite encyclop\u00e9die des n\u0153uds les plus courants dans un plan d\u2019ex\u00e9cution<\/h3>\n<p>Voici une s\u00e9lection des n\u0153uds que vous croiserez r\u00e9guli\u00e8rement dans les plans d\u2019ex\u00e9cution PostgreSQL, avec une explication simple et directe pour chacun :<\/p>\n<ul>\n<li class=\"\" data-start=\"251\" data-end=\"420\">\n<p class=\"\" data-start=\"253\" data-end=\"420\"><strong data-start=\"253\" data-end=\"265\">Seq Scan<\/strong><br data-start=\"265\" data-end=\"268\" \/>Lecture s\u00e9quentielle de toute la table.<br data-start=\"309\" data-end=\"312\" \/><em data-start=\"317\" data-end=\"331\">\u00c0 surveiller<\/em> : Normal sur petites tables, mais sur les grosses, cela peut indiquer un index manquant.<\/p>\n<\/li>\n<li class=\"\" data-start=\"422\" data-end=\"607\">\n<p class=\"\" data-start=\"424\" data-end=\"607\"><strong data-start=\"424\" data-end=\"438\">Index Scan<\/strong><br data-start=\"438\" data-end=\"441\" \/>Parcours d\u2019un index pour chercher les lignes correspondantes.<br data-start=\"504\" data-end=\"507\" \/><em data-start=\"512\" data-end=\"526\">\u00c0 surveiller<\/em> : Rapide si l\u2019index est bien choisi. Peut devenir lent avec beaucoup de <em data-start=\"599\" data-end=\"606\">loops<\/em>.<\/p>\n<\/li>\n<li class=\"\" data-start=\"609\" data-end=\"804\">\n<p class=\"\" data-start=\"611\" data-end=\"804\"><strong data-start=\"611\" data-end=\"630\">Index Only Scan<\/strong><br data-start=\"630\" data-end=\"633\" \/>Comme un Index Scan, mais sans lire la table si toutes les colonnes n\u00e9cessaires sont d\u00e9j\u00e0 dans l\u2019index.<br data-start=\"740\" data-end=\"743\" \/><em data-start=\"748\" data-end=\"762\">\u00c0 surveiller<\/em> : Ultra-performant ! \u00c0 viser si possible.<\/p>\n<\/li>\n<li class=\"\" data-start=\"806\" data-end=\"1031\">\n<p class=\"\" data-start=\"808\" data-end=\"1031\"><strong data-start=\"808\" data-end=\"829\">Bitmap Index Scan<\/strong> + <strong data-start=\"832\" data-end=\"852\">Bitmap Heap Scan<\/strong><br data-start=\"852\" data-end=\"855\" \/>PostgreSQL construit une &#8220;carte&#8221; des lignes \u00e0 lire, puis les r\u00e9cup\u00e8re en une seule passe.<br data-start=\"946\" data-end=\"949\" \/><em data-start=\"954\" data-end=\"968\">\u00c0 surveiller<\/em> : Tr\u00e8s performant pour des filtres avec beaucoup de r\u00e9sultats.<\/p>\n<\/li>\n<li class=\"\" data-start=\"1033\" data-end=\"1244\">\n<p class=\"\" data-start=\"1035\" data-end=\"1244\"><strong data-start=\"1035\" data-end=\"1050\">Nested Loop<\/strong><br data-start=\"1050\" data-end=\"1053\" \/>Pour chaque ligne de la premi\u00e8re table, PostgreSQL cherche dans la seconde.<br data-start=\"1130\" data-end=\"1133\" \/><em data-start=\"1138\" data-end=\"1152\">\u00c0 surveiller<\/em> : Bien sur de petits volumes, mais peut exploser sur de grandes tables (effet quadratique).<\/p>\n<\/li>\n<li class=\"\" data-start=\"1246\" data-end=\"1445\">\n<p class=\"\" data-start=\"1248\" data-end=\"1445\"><strong data-start=\"1248\" data-end=\"1261\">Hash Join<\/strong><br data-start=\"1261\" data-end=\"1264\" \/>PostgreSQL construit une table de hachage en m\u00e9moire pour faire la jointure.<br data-start=\"1342\" data-end=\"1345\" \/><em data-start=\"1350\" data-end=\"1364\">\u00c0 surveiller<\/em> : Performant si la m\u00e9moire le permet. Attention \u00e0 la taille des jeux de donn\u00e9es.<\/p>\n<\/li>\n<li class=\"\" data-start=\"1447\" data-end=\"1621\">\n<p class=\"\" data-start=\"1449\" data-end=\"1621\"><strong data-start=\"1449\" data-end=\"1463\">Merge Join<\/strong><br data-start=\"1463\" data-end=\"1466\" \/>Jointure optimis\u00e9e entre deux sources d\u00e9j\u00e0 tri\u00e9es.<br data-start=\"1518\" data-end=\"1521\" \/><em data-start=\"1526\" data-end=\"1540\">\u00c0 surveiller<\/em> : Excellent en perfs si les colonnes jointes sont index\u00e9es ou tri\u00e9es \u00e0 l\u2019avance.<\/p>\n<\/li>\n<li class=\"\" data-start=\"1623\" data-end=\"1794\">\n<p class=\"\" data-start=\"1625\" data-end=\"1794\"><strong data-start=\"1625\" data-end=\"1638\">Aggregate<\/strong><br data-start=\"1638\" data-end=\"1641\" \/>Calcule une agr\u00e9gation (COUNT, SUM, AVG, &#8230;).<br data-start=\"1696\" data-end=\"1699\" data-is-only-node=\"\" \/><em data-start=\"1704\" data-end=\"1718\">\u00c0 surveiller<\/em> : Peut \u00eatre co\u00fbteux si l\u2019agr\u00e9gation se fait sur de gros volumes sans index.<\/p>\n<\/li>\n<li class=\"\" data-start=\"1796\" data-end=\"1959\">\n<p class=\"\" data-start=\"1798\" data-end=\"1959\"><strong data-start=\"1798\" data-end=\"1806\">Sort<\/strong><br data-start=\"1806\" data-end=\"1809\" \/>Trie les donn\u00e9es selon une ou plusieurs colonnes.<br data-start=\"1860\" data-end=\"1863\" \/><em data-start=\"1868\" data-end=\"1882\">\u00c0 surveiller<\/em> : Peut consommer beaucoup de m\u00e9moire ; un bon index peut \u00e9viter cette \u00e9tape.<\/p>\n<\/li>\n<li class=\"\" data-start=\"1961\" data-end=\"2136\">\n<p class=\"\" data-start=\"1963\" data-end=\"2136\"><strong data-start=\"1963\" data-end=\"1972\">Limit<\/strong><br data-start=\"1972\" data-end=\"1975\" \/>Tronque le r\u00e9sultat \u00e0 N lignes.<br data-start=\"2010\" data-end=\"2013\" \/><em data-start=\"2018\" data-end=\"2032\">\u00c0 surveiller<\/em> : Tr\u00e8s utile combin\u00e9 avec ORDER BY, car PostgreSQL peut s\u2019arr\u00eater d\u00e8s qu\u2019il a assez de lignes tri\u00e9es.<\/p>\n<\/li>\n<li class=\"\" data-start=\"2138\" data-end=\"2327\">\n<p class=\"\" data-start=\"2140\" data-end=\"2327\"><strong data-start=\"2140\" data-end=\"2152\">CTE Scan<\/strong><br data-start=\"2152\" data-end=\"2155\" \/>Utilis\u00e9 quand vous avez une clause WITH\u00a0(CTE \u2013 Common Table Expression).<br data-start=\"2231\" data-end=\"2234\" \/><em data-start=\"2239\" data-end=\"2253\">\u00c0 surveiller<\/em> : Si le CTE n\u2019est pas mat\u00e9rialis\u00e9, il peut \u00eatre recalcul\u00e9 \u00e0 chaque appel.<\/p>\n<\/li>\n<\/ul>\n<h2>Options utiles de EXPLAIN \/ EXPLAIN ANALYZE<\/h2>\n<p>La commande EXPLAIN (et sa variante EXPLAIN ANALYZE) peut \u00eatre enrichie avec plusieurs options facultatives pour mieux comprendre ce que PostgreSQL fait avec vos requ\u00eates. Voici une pr\u00e9sentation des principales.<\/p>\n<h4>ANALYZE : ex\u00e9cuter la requ\u00eate pour de vrai<\/h4>\n<p>Cette option (souvent appel\u00e9e \u201cEXPLAIN ANALYZE\u201d) <strong>ex\u00e9cute r\u00e9ellement la requ\u00eate<\/strong> et mesure les temps d&#8217;ex\u00e9cution.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'foo@example.com'; <\/pre>\n<p>Sans cette option, PostgreSQL ne fait qu\u2019estimer le plan, il ne l\u2019ex\u00e9cute pas r\u00e9ellement.<\/p>\n<h4>VERBOSE: plus de d\u00e9tails sur les colonnes et les expressions<\/h4>\n<p>Affiche le nom exact des colonnes internes et les expressions utilis\u00e9es dans chaque \u00e9tape du plan.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, VERBOSE) SELECT name FROM users WHERE age &amp;gt; 30; <\/pre>\n<p>Tr\u00e8s utile quand on travaille avec des fonctions, des agr\u00e9gats ou des vues complexes.<\/p>\n<h4>BUFFERS : d\u00e9tail des lectures m\u00e9moire et disque<\/h4>\n<p>Montre combien de blocs de donn\u00e9es ont \u00e9t\u00e9 lus en m\u00e9moire (cache partag\u00e9) et depuis le disque.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE status = 'completed'; <\/pre>\n<p>Id\u00e9al pour identifier si une requ\u00eate est ralentie par des acc\u00e8s disques trop nombreux.<\/p>\n<h4>WAL : suivi des \u00e9critures dans le journal de transactions<\/h4>\n<p>Affiche l\u2019impact de la requ\u00eate sur le WAL (Write-Ahead Logging), c\u2019est-\u00e0-dire les \u00e9critures n\u00e9cessaires \u00e0 la durabilit\u00e9.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, WAL) INSERT INTO logs SELECT * FROM events; <\/pre>\n<p>Surtout utile pour comprendre le co\u00fbt cach\u00e9 des requ\u00eates d\u2019\u00e9criture.<\/p>\n<h4>COSTS : afficher ou masquer les co\u00fbts estim\u00e9s<\/h4>\n<p>Permet de d\u00e9sactiver les lignes cost=&#8230; si on veut se concentrer uniquement sur les temps r\u00e9els (actual time).<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM users; <\/pre>\n<p>Pratique pour all\u00e9ger la lecture d\u2019un plan quand on n\u2019a pas besoin des estimations.<\/p>\n<h4>SETTINGS : voir les param\u00e8tres ayant influenc\u00e9 le plan<\/h4>\n<p>Affiche les param\u00e8tres de configuration de PostgreSQL qui ont eu un impact sur la g\u00e9n\u00e9ration du plan.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, SETTINGS) SELECT * FROM users; <\/pre>\n<p>Tr\u00e8s utile pour le debug avanc\u00e9, ou si certains param\u00e8tres sont modifi\u00e9s via SET.<\/p>\n<h4>SUMMARY : afficher ou non les temps globaux<\/h4>\n<p>Contr\u00f4le l\u2019affichage du r\u00e9sum\u00e9 final (Planning Time, Execution Time).<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, SUMMARY OFF) SELECT * FROM users; <\/pre>\n<p>Par d\u00e9faut activ\u00e9, mais vous pouvez le d\u00e9sactiver si vous ne souhaitez pas ces infos \u00e0 la fin du plan.<\/p>\n<h4>TIMING : activer ou d\u00e9sactiver la mesure des temps internes<\/h4>\n<p>PostgreSQL mesure le actual time pour chaque n\u0153ud du plan. Cette option permet de d\u00e9sactiver ces mesures (utile pour les tr\u00e8s petites requ\u00eates ou les benchmarks massifs).<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM users; <\/pre>\n<p>D\u00e9sactive les mesures fines, ce qui peut l\u00e9g\u00e8rement am\u00e9liorer les performances du plan d\u2019analyse lui-m\u00eame.<\/p>\n<h4>FORMAT : changer la sortie (TEXT, JSON, YAML)<\/h4>\n<p>Permet d\u2019obtenir un plan dans un format structur\u00e9 (parfait pour les outils externes comme explain.dalibo.com).<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\"> EXPLAIN (ANALYZE, FORMAT JSON) SELECT * FROM users; <\/pre>\n<p>Pratique pour g\u00e9n\u00e9rer un plan visuel, l\u2019analyser en script, ou l\u2019int\u00e9grer dans des outils de perf.<\/p>\n<h2>OK super, mais une fois qu&#8217;on sait \u00e7a, on en fait quoi ?<\/h2>\n<p data-start=\"260\" data-end=\"525\">On pourrait passer des heures \u00e0 d\u00e9cortiquer les diff\u00e9rentes lignes d&#8217;un plan d&#8217;ex\u00e9cution sans pour autant avancer plus que \u00e7a. L&#8217;important est maintenant de savoir quoi en faire. Parce que c&#8217;est bien beau de l&#8217;afficher, encore faut-il savoir quoi y chercher.<\/p>\n<p data-start=\"260\" data-end=\"525\">Voici une petite liste non exhaustive des indices \u00e0 rep\u00e9rer dans un plan d&#8217;ex\u00e9cution qui pourraient vous mener \u00e0 une raison pour la lenteur de votre requ\u00eate :<\/p>\n<h3>&#x1f50e; Une lecture s\u00e9quentielle sur une grosse table :<\/h3>\n<p class=\"\" data-start=\"659\" data-end=\"678\"><strong data-start=\"659\" data-end=\"674\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">Seq Scan on users  (rows=100000)<br \/>\n<\/code><\/div>\n<\/div>\n<p><strong data-start=\"725\" data-end=\"755\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"755\" data-end=\"758\" \/>PostgreSQL lit toute la table ligne par ligne. C\u2019est tr\u00e8s lent et parfaitement inutile, surtout si vous ne voulez que quelques lignes de cette table.<\/p>\n<p><strong data-start=\"861\" data-end=\"887\">Comment le voir<\/strong><\/p>\n<ul data-start=\"890\" data-end=\"992\">\n<li class=\"\" data-start=\"890\" data-end=\"914\">On note la pr\u00e9sence d&#8217;un n\u0153ud SEQ SCAN dans notre plan d&#8217;ex\u00e9cution<\/li>\n<li class=\"\" data-start=\"915\" data-end=\"992\">Nombre de lignes parcourues tr\u00e8s \u00e9lev\u00e9 (puce rows ou pr\u00e9sence de row removed by filter)<\/li>\n<\/ul>\n<p><strong data-start=\"994\" data-end=\"1010\">Solutions<\/strong><\/p>\n<ul data-start=\"1011\" data-end=\"1184\">\n<li class=\"\" data-start=\"1011\" data-end=\"1085\">Ajouter un index sur la colonne filtr\u00e9e peut souvent aider dans ce genre de cas.<\/li>\n<li class=\"\" data-start=\"1086\" data-end=\"1184\">R\u00e9\u00e9crire la requ\u00eate pour qu\u2019elle utilise une clause mieux optimis\u00e9e (exclusion, inclusion&#8230;)<\/li>\n<\/ul>\n<h3>&#x1f50e; Trop de Loop<\/h3>\n<p class=\"\" data-start=\"1242\" data-end=\"1261\"><strong data-start=\"1242\" data-end=\"1257\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">Index Scan using users_pkey on users  (loops=10000)<br \/>\n<\/code><\/div>\n<\/div>\n<p class=\"\" data-start=\"1327\" data-end=\"1489\"><strong data-start=\"1327\" data-end=\"1357\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"1357\" data-end=\"1360\" \/>Une op\u00e9ration lente est r\u00e9p\u00e9t\u00e9e pour chaque ligne de l\u2019op\u00e9ration ext\u00e9rieure, souvent dans une jointure en boucle (Nested Loop).<\/p>\n<p class=\"\" data-start=\"1491\" data-end=\"1519\"><strong data-start=\"1491\" data-end=\"1517\">Comment le voir<\/strong><\/p>\n<ul data-start=\"1520\" data-end=\"1575\">\n<li data-start=\"1520\" data-end=\"1535\">Nombre de loop tr\u00e8s \u00e9lev\u00e9s<\/li>\n<li class=\"\" data-start=\"1520\" data-end=\"1535\">Souvent pr\u00e9c\u00e9d\u00e9 d&#8217;un Nested Loop plus haut dans le plan d&#8217;ex\u00e9cution<\/li>\n<\/ul>\n<p class=\"\" data-start=\"1577\" data-end=\"1593\"><strong data-start=\"1577\" data-end=\"1593\">Solutions<\/strong><\/p>\n<ul data-start=\"1594\" data-end=\"1764\">\n<li class=\"\" data-start=\"1594\" data-end=\"1657\">\n<p class=\"\" data-start=\"1596\" data-end=\"1657\">Changer le type de jointure pour quelque chose de plus simple a traiter (hash join ou merge join)<\/p>\n<\/li>\n<li class=\"\" data-start=\"1658\" data-end=\"1715\">\n<p class=\"\" data-start=\"1660\" data-end=\"1715\">R\u00e9\u00e9crire la requ\u00eate pour faire moins de &#8220;aller-retours&#8221;<\/p>\n<\/li>\n<li class=\"\" data-start=\"1716\" data-end=\"1764\">\n<p class=\"\" data-start=\"1718\" data-end=\"1764\">Ajouter des index pour faciliter les jointures<\/p>\n<\/li>\n<\/ul>\n<h3 data-start=\"1718\" data-end=\"1764\">&#x1f50e; Des estimations loin de la r\u00e9alit\u00e9<\/h3>\n<p class=\"\" data-start=\"1826\" data-end=\"1845\"><strong data-start=\"1826\" data-end=\"1841\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">rows=10   actual rows=10000<br \/>\n<\/code><\/div>\n<\/div>\n<p class=\"\" data-start=\"1887\" data-end=\"2017\"><strong data-start=\"1887\" data-end=\"1917\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"1917\" data-end=\"1920\" \/>PostgreSQL s\u2019est tromp\u00e9 dans son estimation, ce qui l\u2019a peut-\u00eatre men\u00e9 \u00e0 choisir un mauvais plan.<\/p>\n<p class=\"\" data-start=\"2019\" data-end=\"2179\"><strong data-start=\"2019\" data-end=\"2045\">Comment le voir<\/strong><br data-start=\"2045\" data-end=\"2048\" \/>Comparez le nombre de rows (estimation) avec les actual rows. Si l\u2019\u00e9cart est tr\u00e8s important, les statistiques sont probablement obsol\u00e8tes.<\/p>\n<p class=\"\" data-start=\"2181\" data-end=\"2197\"><strong data-start=\"2181\" data-end=\"2197\">Solutions<\/strong><\/p>\n<ul data-start=\"2198\" data-end=\"2421\">\n<li class=\"\" data-start=\"2198\" data-end=\"2259\">\n<p class=\"\" data-start=\"2200\" data-end=\"2259\">Rafraichir les statistiques dans ce cas ne peut pas faire de mal : Analyze ou vaccuum analyze si n\u00e9cessaire.<\/p>\n<\/li>\n<li class=\"\" data-start=\"2260\" data-end=\"2352\">\n<p class=\"\" data-start=\"2262\" data-end=\"2352\">Ajuster les statistiques<\/p>\n<\/li>\n<li class=\"\" data-start=\"2353\" data-end=\"2421\">\n<p class=\"\" data-start=\"2355\" data-end=\"2421\">\u00c9viter les expressions trop complexes qui biaisent les estimations<\/p>\n<\/li>\n<\/ul>\n<h3 data-start=\"2355\" data-end=\"2421\">&#x1f50e; Un tri qui consomme trop<\/h3>\n<p class=\"\" data-start=\"2494\" data-end=\"2513\"><strong data-start=\"2494\" data-end=\"2509\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">Sort  (Sort Method: quicksort \/ external merge, Memory: 100MB)<br \/>\n<\/code><\/div>\n<\/div>\n<p class=\"\" data-start=\"2590\" data-end=\"2723\"><strong data-start=\"2590\" data-end=\"2620\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"2620\" data-end=\"2623\" \/>Le tri est trop gros, PostgreSQL n\u2019arrive plus \u00e0 le faire en RAM \u2192 il passe sur disque \u2192 c\u2019est lent.<\/p>\n<p class=\"\" data-start=\"2725\" data-end=\"2753\"><strong data-start=\"2725\" data-end=\"2751\">Comment le voir<\/strong><\/p>\n<ul data-start=\"2754\" data-end=\"2879\">\n<li class=\"\" data-start=\"2754\" data-end=\"2813\">\n<p class=\"\" data-start=\"2756\" data-end=\"2813\">N\u0153ud SORT avec un SORT METHOD lent (EXTERNAL MERGE)<\/p>\n<\/li>\n<li class=\"\" data-start=\"2814\" data-end=\"2843\">\n<p class=\"\" data-start=\"2816\" data-end=\"2843\">Consommation m\u00e9moire \u00e9lev\u00e9e<\/p>\n<\/li>\n<li class=\"\" data-start=\"2844\" data-end=\"2879\">\n<p class=\"\" data-start=\"2846\" data-end=\"2879\">\u00c9tape qui prend beaucoup de temps<\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"2881\" data-end=\"2897\"><strong data-start=\"2881\" data-end=\"2897\">Solutions<\/strong><\/p>\n<ul data-start=\"2898\" data-end=\"3077\">\n<li class=\"\" data-start=\"2898\" data-end=\"2962\">\n<p class=\"\" data-start=\"2900\" data-end=\"2962\">Ajouter un index sur les colonnes utilis\u00e9es dans le ORDER BY<\/p>\n<\/li>\n<li class=\"\" data-start=\"2963\" data-end=\"3027\">\n<p class=\"\" data-start=\"2965\" data-end=\"3027\">R\u00e9duire la quantit\u00e9 de lignes \u00e0 trier avec un LIMIT\u00a0en amont<\/p>\n<\/li>\n<li class=\"\" data-start=\"3028\" data-end=\"3077\">\n<p class=\"\" data-start=\"3030\" data-end=\"3077\">\u00c9viter les sous-requ\u00eates non filtr\u00e9es avant tri<\/p>\n<\/li>\n<\/ul>\n<h3 data-start=\"3030\" data-end=\"3077\">&#x1f50e; Une clause Limit inefficace<\/h3>\n<p class=\"\" data-start=\"3164\" data-end=\"3183\"><strong data-start=\"3164\" data-end=\"3179\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">Sort \u2192 Limit<br \/>\n<\/code><\/div>\n<\/div>\n<p class=\"\" data-start=\"3210\" data-end=\"3355\"><strong data-start=\"3210\" data-end=\"3240\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"3240\" data-end=\"3243\" \/>PostgreSQL trie <em data-start=\"3259\" data-end=\"3266\">toute<\/em> la table avant d\u2019en extraire 10 lignes. S\u2019il y a 1 million de lignes, c\u2019est pas optimal.<\/p>\n<p class=\"\" data-start=\"3357\" data-end=\"3385\"><strong data-start=\"3357\" data-end=\"3383\">Comment le voir<\/strong><\/p>\n<ul data-start=\"3386\" data-end=\"3479\">\n<li class=\"\" data-start=\"3386\" data-end=\"3422\">\n<p class=\"\" data-start=\"3388\" data-end=\"3422\">Le SORT est au-dessus du LIMIT<\/p>\n<\/li>\n<li class=\"\" data-start=\"3423\" data-end=\"3479\">\n<p class=\"\" data-start=\"3425\" data-end=\"3479\">Le tri prend beaucoup de temps m\u00eame pour un LIMIT 10<\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"3481\" data-end=\"3497\"><strong data-start=\"3481\" data-end=\"3497\">Solutions<\/strong><\/p>\n<ul data-start=\"3498\" data-end=\"3678\">\n<li class=\"\" data-start=\"3498\" data-end=\"3588\">\n<p class=\"\" data-start=\"3500\" data-end=\"3588\">Utiliser un index qui permet de lire d\u00e9j\u00e0 tri\u00e9 (ORDER BY created_at DESC\u00a0\u2192 index DESC)<\/p>\n<\/li>\n<li class=\"\" data-start=\"3589\" data-end=\"3678\">\n<p class=\"\" data-start=\"3591\" data-end=\"3678\">Repenser la requ\u00eate pour \u00e9viter le tri global (ex : pr\u00e9filtrage ou pagination efficace)<\/p>\n<\/li>\n<\/ul>\n<h3 data-start=\"3591\" data-end=\"3678\">&#x1f50e; Des filtres appliqu\u00e9s trop tard<\/h3>\n<p class=\"\" data-start=\"3744\" data-end=\"3763\"><strong data-start=\"3744\" data-end=\"3759\">Sympt\u00f4me<\/strong> :<\/p>\n<div class=\"contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary\">\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">Rows Removed by Filter: 999000<br \/>\n<\/code><\/div>\n<\/div>\n<p class=\"\" data-start=\"3808\" data-end=\"3948\"><strong data-start=\"3808\" data-end=\"3838\">Pourquoi c\u2019est un souci<\/strong><br data-start=\"3838\" data-end=\"3841\" \/>Le filtre est appliqu\u00e9 apr\u00e8s avoir lu la majorit\u00e9 des lignes \u2192 PostgreSQL fait beaucoup de travail inutile.<\/p>\n<p class=\"\" data-start=\"3950\" data-end=\"3978\"><strong data-start=\"3950\" data-end=\"3976\">Comment le voir<\/strong><\/p>\n<ul data-start=\"3979\" data-end=\"4071\">\n<li class=\"\" data-start=\"3979\" data-end=\"4023\">\n<p class=\"\" data-start=\"3981\" data-end=\"4023\">Pr\u00e9sence de FILTER (&#8230;) en bas de plan<\/p>\n<\/li>\n<li class=\"\" data-start=\"4024\" data-end=\"4071\">\n<p class=\"\" data-start=\"4026\" data-end=\"4071\">Tr\u00e8s grand nombre de ROWS REMOVED BY FILTER<\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"4073\" data-end=\"4089\"><strong data-start=\"4073\" data-end=\"4089\">Solutions<\/strong><\/p>\n<ul data-start=\"4090\" data-end=\"4311\">\n<li class=\"\" data-start=\"4090\" data-end=\"4120\">\n<p class=\"\" data-start=\"4092\" data-end=\"4120\">Indexer la colonne du filtre<\/p>\n<\/li>\n<li class=\"\" data-start=\"4121\" data-end=\"4203\">\n<p class=\"\" data-start=\"4123\" data-end=\"4203\">R\u00e9\u00e9crire la requ\u00eate pour que le filtre soit pris en compte plus t\u00f4t dans le plan<\/p>\n<\/li>\n<li class=\"\" data-start=\"4204\" data-end=\"4311\">\n<p class=\"\" data-start=\"4206\" data-end=\"4311\">\u00c9viter les fonctions non indexables dans le WHERE (ex : LOWER(email)\u00a0\u2192 pr\u00e9f\u00e9rer un index fonctionnel)<\/p>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h1>Conclusion<\/h1>\n<p class=\"\" data-start=\"313\" data-end=\"605\">Comme souvent avec PostgreSQL, il est difficile \u2014 voire impossible \u2014 de tout couvrir en un seul article. Chaque plan d&#8217;ex\u00e9cution est unique, chaque requ\u00eate a ses subtilit\u00e9s, et chaque base de donn\u00e9es a ses petites surprises. Il n\u2019existe pas de recette miracle qui marcherait \u00e0 tous les coups.<\/p>\n<p class=\"\" data-start=\"607\" data-end=\"813\">Mais avec les bons r\u00e9flexes, quelques outils et un peu de m\u00e9thode, on peut rapidement progresser : rep\u00e9rer les sympt\u00f4mes, lire les signes, poser les bonnes questions\u2026 et surtout, tester, encore et toujours.<\/p>\n<p class=\"\" data-start=\"607\" data-end=\"813\">EXPLAIN ANALYZE n\u2019est pas r\u00e9serv\u00e9 aux DBA ou aux experts en perfs. C\u2019est un compagnon de route pour toute personne qui \u00e9crit des requ\u00eates, et qui veut comprendre ce qui se passe sous le capot.<\/p>\n<p class=\"\" data-start=\"1011\" data-end=\"1213\">Et si vous vous sentez encore un peu perdu\u00b7e face \u00e0 un plan trop verbeux : pas de panique. Avec l\u2019habitude, \u00e7a devient un langage qu\u2019on apprend \u00e0 lire presque instinctivement. Et \u00e7a commence maintenant.<\/p>\n<a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox\" data-provider=\"twitter\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Twitter\" href=\"https:\/\/twitter.com\/intent\/tweet?url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"twitter\" title=\"Share on Twitter\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/twitter.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox\" data-provider=\"linkedin\" target=\"_blank\" rel=\"nofollow\" title=\"Share on Linkedin\" href=\"https:\/\/www.linkedin.com\/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697&#038;title=PostgreSQL%20Basics%20%3A%20lire%20un%20plan%20d%E2%80%99ex%C3%A9cution%20comme%20un%C2%B7e%20pro%20%28ou%20presque%29\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"linkedin\" title=\"Share on Linkedin\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/linkedin.png\" \/><\/a><a class=\"synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox\" data-provider=\"mail\" rel=\"nofollow\" title=\"Share by email\" href=\"mailto:?subject=PostgreSQL%20Basics%20%3A%20lire%20un%20plan%20d%E2%80%99ex%C3%A9cution%20comme%20un%C2%B7e%20pro%20%28ou%20presque%29&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2Findex.php%2Fwp-json%2Fwp%2Fv2%2Fposts%2F10697\" style=\"font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px\"><img loading=\"lazy\" decoding=\"async\" alt=\"mail\" title=\"Share by email\" class=\"synved-share-image synved-social-image synved-social-image-share\" width=\"24\" height=\"24\" style=\"display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none\" src=\"https:\/\/blog.capdata.fr\/wp-content\/plugins\/social-media-feather\/synved-social\/image\/social\/regular\/48x48\/mail.png\" \/><\/a>","protected":false},"excerpt":{"rendered":"<p>Introduction : Quand une requ\u00eate PostgreSQL est consid\u00e9r\u00e9e comme lente ou que ses performances se d\u00e9gradent soudainement, il y a un r\u00e9flexe \u00e0 toujours avoir : utiliser les plans d&#8217;ex\u00e9cution. Je n\u2019apprends certainement rien \u00e0 la majorit\u00e9 des personnes qui&hellip; <a href=\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\" class=\"more-link\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":41,"featured_media":10720,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[266],"tags":[488,399,431],"class_list":["post-10697","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-postgresql","tag-explain","tag-plan-dexecution","tag-postgresql"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.8 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>PostgreSQL Basics : lire un plan d&#039;ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PostgreSQL Basics : lire un plan d&#039;ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG\" \/>\n<meta property=\"og:description\" content=\"Introduction : Quand une requ\u00eate PostgreSQL est consid\u00e9r\u00e9e comme lente ou que ses performances se d\u00e9gradent soudainement, il y a un r\u00e9flexe \u00e0 toujours avoir : utiliser les plans d&#8217;ex\u00e9cution. Je n\u2019apprends certainement rien \u00e0 la majorit\u00e9 des personnes qui&hellip; Continuer la lecture &rarr;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\" \/>\n<meta property=\"og:site_name\" content=\"Capdata TECH BLOG\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-13T15:20:05+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-13T15:29:21+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/particle-accelerator-1903637_1280.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1280\" \/>\n\t<meta property=\"og:image:height\" content=\"960\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Sarah FAVEERE\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sarah FAVEERE\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\"},\"author\":{\"name\":\"Sarah FAVEERE\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/person\/686f2452f7ec79115d31e41c230a9da2\"},\"headline\":\"PostgreSQL Basics : lire un plan d&#8217;ex\u00e9cution comme un\u00b7e pro (ou presque)\",\"datePublished\":\"2025-05-13T15:20:05+00:00\",\"dateModified\":\"2025-05-13T15:29:21+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\"},\"wordCount\":2867,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/blog.capdata.fr\/#organization\"},\"keywords\":[\"explain\",\"Plan d'ex\u00e9cution\",\"PostgreSQL\"],\"articleSection\":[\"PostgreSQL\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\",\"url\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\",\"name\":\"PostgreSQL Basics : lire un plan d'ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG\",\"isPartOf\":{\"@id\":\"https:\/\/blog.capdata.fr\/#website\"},\"datePublished\":\"2025-05-13T15:20:05+00:00\",\"dateModified\":\"2025-05-13T15:29:21+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/blog.capdata.fr\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PostgreSQL Basics : lire un plan d&#8217;ex\u00e9cution comme un\u00b7e pro (ou presque)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.capdata.fr\/#website\",\"url\":\"https:\/\/blog.capdata.fr\/\",\"name\":\"Capdata TECH BLOG\",\"description\":\"Le blog technique sur les bases de donn\u00e9es de CAP DATA Consulting\",\"publisher\":{\"@id\":\"https:\/\/blog.capdata.fr\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.capdata.fr\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/blog.capdata.fr\/#organization\",\"name\":\"Capdata TECH BLOG\",\"url\":\"https:\/\/blog.capdata.fr\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp\",\"contentUrl\":\"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp\",\"width\":800,\"height\":254,\"caption\":\"Capdata TECH BLOG\"},\"image\":{\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.linkedin.com\/company\/cap-data-consulting\/mycompany\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.capdata.fr\/#\/schema\/person\/686f2452f7ec79115d31e41c230a9da2\",\"name\":\"Sarah FAVEERE\",\"sameAs\":[\"http:\/\/blog.capdata.fr\"],\"url\":\"https:\/\/blog.capdata.fr\/index.php\/author\/sfaveere\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"PostgreSQL Basics : lire un plan d'ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/","og_locale":"fr_FR","og_type":"article","og_title":"PostgreSQL Basics : lire un plan d'ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG","og_description":"Introduction : Quand une requ\u00eate PostgreSQL est consid\u00e9r\u00e9e comme lente ou que ses performances se d\u00e9gradent soudainement, il y a un r\u00e9flexe \u00e0 toujours avoir : utiliser les plans d&#8217;ex\u00e9cution. Je n\u2019apprends certainement rien \u00e0 la majorit\u00e9 des personnes qui&hellip; Continuer la lecture &rarr;","og_url":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/","og_site_name":"Capdata TECH BLOG","article_published_time":"2025-05-13T15:20:05+00:00","article_modified_time":"2025-05-13T15:29:21+00:00","og_image":[{"width":1280,"height":960,"url":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2025\/04\/particle-accelerator-1903637_1280.jpg","type":"image\/jpeg"}],"author":"Sarah FAVEERE","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"Sarah FAVEERE","Dur\u00e9e de lecture estim\u00e9e":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#article","isPartOf":{"@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/"},"author":{"name":"Sarah FAVEERE","@id":"https:\/\/blog.capdata.fr\/#\/schema\/person\/686f2452f7ec79115d31e41c230a9da2"},"headline":"PostgreSQL Basics : lire un plan d&#8217;ex\u00e9cution comme un\u00b7e pro (ou presque)","datePublished":"2025-05-13T15:20:05+00:00","dateModified":"2025-05-13T15:29:21+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/"},"wordCount":2867,"commentCount":0,"publisher":{"@id":"https:\/\/blog.capdata.fr\/#organization"},"keywords":["explain","Plan d'ex\u00e9cution","PostgreSQL"],"articleSection":["PostgreSQL"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/","url":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/","name":"PostgreSQL Basics : lire un plan d'ex\u00e9cution comme un\u00b7e pro (ou presque) - Capdata TECH BLOG","isPartOf":{"@id":"https:\/\/blog.capdata.fr\/#website"},"datePublished":"2025-05-13T15:20:05+00:00","dateModified":"2025-05-13T15:29:21+00:00","breadcrumb":{"@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.capdata.fr\/index.php\/postgresql-basics-lire-un-plan-dexecution\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/blog.capdata.fr\/"},{"@type":"ListItem","position":2,"name":"PostgreSQL Basics : lire un plan d&#8217;ex\u00e9cution comme un\u00b7e pro (ou presque)"}]},{"@type":"WebSite","@id":"https:\/\/blog.capdata.fr\/#website","url":"https:\/\/blog.capdata.fr\/","name":"Capdata TECH BLOG","description":"Le blog technique sur les bases de donn\u00e9es de CAP DATA Consulting","publisher":{"@id":"https:\/\/blog.capdata.fr\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.capdata.fr\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/blog.capdata.fr\/#organization","name":"Capdata TECH BLOG","url":"https:\/\/blog.capdata.fr\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/","url":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp","contentUrl":"https:\/\/blog.capdata.fr\/wp-content\/uploads\/2023\/01\/logo_capdata.webp","width":800,"height":254,"caption":"Capdata TECH BLOG"},"image":{"@id":"https:\/\/blog.capdata.fr\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.linkedin.com\/company\/cap-data-consulting\/mycompany\/"]},{"@type":"Person","@id":"https:\/\/blog.capdata.fr\/#\/schema\/person\/686f2452f7ec79115d31e41c230a9da2","name":"Sarah FAVEERE","sameAs":["http:\/\/blog.capdata.fr"],"url":"https:\/\/blog.capdata.fr\/index.php\/author\/sfaveere\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/10697","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/comments?post=10697"}],"version-history":[{"count":21,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/10697\/revisions"}],"predecessor-version":[{"id":10722,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/posts\/10697\/revisions\/10722"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/media\/10720"}],"wp:attachment":[{"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/media?parent=10697"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/categories?post=10697"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.capdata.fr\/index.php\/wp-json\/wp\/v2\/tags?post=10697"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}