menu
languageFrançais
save_alt Télécharger

Explain Visualizer

Visualise les plans d’exécution de tes requêtes sous forme de diagramme interactif et repère les parties lentes d’une requête.

par rathboma Plugin communautaire

Readme

Ce texte a été écrit par un tiers et traduit automatiquement. Lire l'original en anglais

Un plugin Beekeeper Studio qui transforme les plans d’exécution en diagramme interactif, pour trouver facilement la partie lente d’une requête.

Démonstration : clic droit sur une requête, ouverture de son plan, lancement d’Explain Analyze et accès direct à l’étape lente

  • Diagramme du plan, coloré selon la part du temps d’exécution (ou du coût estimé) de chaque étape. Plus un lien est large, plus il transporte de lignes.
  • Vue Tableau dans le style de explain.depesz.com, triable par temps propre.
  • Détails de chaque étape : temps propre et total, lignes réelles vs estimées, boucles, coûts, conditions, buffers, mémoire et disque utilisés, workers.
  • Insights qui signalent les problèmes courants : scans complets qui écartent la plupart des lignes, tables parcourues une fois par ligne d’une jointure, tris et hachages qui débordent sur disque, sous-requêtes dépendantes, index temporaires que SQLite construit à chaque exécution, grosses erreurs d’estimation de lignes, et plus encore.
  • Sortie brute du plan, prête à copier ou enregistrer.

Plus de détails dans les captures d’écran ci-dessous.

Bases de données prises en charge

Base de données Explain Explain Analyze Coller un plan
PostgreSQL Oui Oui JSON et texte psql
MySQL Oui 8.0.18+, requêtes de lecture Arbre, JSON (les deux versions) et le tableau classique
MariaDB Oui 10.1+, requêtes de lecture JSON et le tableau classique
SQLite, LibSQL Oui Non : SQLite ne mesure pas le temps des étapes L’arbre du shell sqlite3, ou les lignes de résultat
SQL Server Prévu    

Testé avec PostgreSQL 12, 17 et 18 ; MySQL 5.7, 8.0.17, 8.0, 8.4 et 9.7 ; MariaDB 10.4, 10.6 et 11.4 ; et SQLite 3.16, 3.27 et 3.45. Le plugin repose sur un modèle de plan indépendant de la base, donc chaque base n’a besoin que d’un adaptateur (voir Ajouter une base de données).

Utilisation

Il y a plusieurs façons de l’ouvrir :

  • Éditeur de requêtes : fais un clic droit sur une requête et choisis Visualize Explain Plan. Le texte sélectionné est utilisé, sinon la requête sous le curseur.
  • Menu nouvel onglet : ouvre Explain Visualizer, écris une requête et choisis Explain ou Explain Analyze.
  • Grille de résultats : après avoir lancé EXPLAIN toi-même, fais un clic droit sur le plan et choisis Visualize Explain Plan:
    • un plan dans une seule cellule (FORMAT JSON de PostgreSQL, arbre ou JSON de MySQL) : clic droit sur la cellule ;
    • un plan réparti sur plusieurs lignes (sortie texte de PostgreSQL) : clic droit sur l’en-tête de la colonne QUERY PLAN ;
    • un tableau (EXPLAIN classique de MySQL, EXPLAIN QUERY PLAN de SQLite) : sélectionne toutes ses lignes et colonnes, puis fais un clic droit sur la sélection. Pour SQLite, la colonne detail seule fonctionne aussi, sans l’imbrication.
  • Paste Plan : colle une sortie EXPLAIN, par exemple depuis psql, le shell mysql ou sqlite3, ou envoyée par un collègue.

Le menu contextuel de l’éditeur de requêtes avec Visualize Explain Plan

Explain et Explain Analyze

Explain demande son plan à la base sans exécuter la requête. C’est toujours sans risque, donc il se lance automatiquement quand l’onglet s’ouvre depuis l’éditeur.

Explain Analyze exécute la requête et mesure chaque étape. La façon de garder ça sûr dépend de la base, voir ci-dessous. Si le texte de l’éditeur commence déjà par EXPLAIN ANALYZE (ou l’équivalent de la base), le plugin l’utilise ; si la requête ne peut pas être analysée, elle est expliquée à la place, et la raison s’affiche sous l’éditeur.

PostgreSQL

Le plugin envoie EXPLAIN ANALYZE ...; ROLLBACK; en une seule requête : les deux instructions partagent une transaction implicite et tout ce que la requête a modifié est annulé. Ça inclut les simples SELECT, qui peuvent appeler des fonctions qui écrivent. Si la requête échoue, la transaction est annulée d’elle-même et la connexion du pool reste propre. Les effets hors transaction, comme les valeurs de séquence, ne sont pas annulés.

Les requêtes qui modifient des données (INSERT, UPDATE, DELETE, MERGE, CTE qui modifient des données…) ne sont analysées qu’après confirmation. Le plugin lit la requête avec un lexer qui suit les règles de PostgreSQL pour les chaînes, commentaires et dollar quotes, et refuse toute saisie contenant plus d’une instruction.

Sur les connexions en lecture seule, Beekeeper Studio n’autorise pas le ROLLBACK : les requêtes qui modifient des données peuvent seulement être expliquées, et Explain Analyze ne se lance jamais automatiquement. Un SELECT qui appelle une fonction qui écrit n’y est pas détecté, comme quand tu l’exécutes dans un onglet de requête.

Le menu Options active ou désactive BUFFERS, VERBOSE, SETTINGS et WAL. Les options que le serveur ne prend pas en charge sont désactivées. Les requêtes avec des paramètres $1 sont expliquées avec GENERIC_PLAN sur PostgreSQL 16+.

MySQL et MariaDB

Le plugin lit la version du serveur et utilise le format de plan le plus récent disponible :

Serveur Explain Explain Analyze
MySQL 8.0.18+ EXPLAIN FORMAT=TREE EXPLAIN ANALYZE
MySQL 5.6 à 8.0.17 EXPLAIN FORMAT=JSON Non disponible
MariaDB 10.1+ EXPLAIN FORMAT=JSON ANALYZE FORMAT=JSON
Serveurs plus anciens Le tableau EXPLAIN classique Non disponible

Les UPDATE et DELETE sur une seule table n’ont pas d’arbre ; le plugin se rabat sur FORMAT=JSON, et sur MySQL 9, dont le JSON ne peut pas les afficher non plus, sur le tableau classique. TiDB est reconnu et refusé plutôt que mal interprété.

Explain Analyze n’exécute que les requêtes de lecture. Contrairement à PostgreSQL, on ne peut pas compter sur MySQL pour annuler une requête échouée : envoyée sous la forme START TRANSACTION; ...; ROLLBACK;, une requête en échec laisserait la transaction ouverte sur une connexion du pool que d’autres onglets utilisent ensuite. Un SELECT qui appelle une fonction stockée qui écrit valide ce que la fonction a fait, comme quand tu l’exécutes dans un onglet de requête. Sur MariaDB, le mode lecture seule de Beekeeper Studio refuse l’instruction ANALYZE, donc Explain Analyze n’est pas disponible sur les connexions en lecture seule.

La requête est lue avec les règles de MySQL : commentaires # et -- qui ne se terminent qu’à un saut de ligne, échappements par antislash, chaînes "..." et commentaires /*! ... */, dont MySQL exécute le contenu. NO_BACKSLASH_ESCAPES et ANSI_QUOTES changent la fin des chaînes, et une connexion du pool peut avoir l’un ou l’autre : une requête doit donc se lire comme une seule instruction, et comme une lecture, quelle que soit leur combinaison. Un texte qu’un serveur pourrait lire de plusieurs façons est refusé plutôt que deviné : un commentaire conditionné par version /*!50700 ... */ ou propre à MariaDB /*M! ... */, un retour chariot dans un commentaire --, ou un caractère NUL.

MariaDB mesure le temps par table plutôt que par étape : le temps d’une jointure ou d’une table temporaire correspond à ce que la requête a pris en plus des tables lues.

SQLite

EXPLAIN QUERY PLAN montre quelles tables sont lues, dans quel ordre et avec quel index. SQLite ne donne ni coûts, ni nombres de lignes, ni temps : le diagramme montre les étapes et la vue tableau leurs détails, et il n’y a pas d’Explain Analyze. Les insights signalent les index temporaires que SQLite construit à chaque exécution, les tables parcourues une fois par ligne d’une jointure ou d’une sous-requête corrélée, et les tris dans un B-tree temporaire. Les connexions LibSQL utilisent le même moteur.

Beekeeper Studio découpe le texte SQLite en instructions avec son propre parser avant de les exécuter : une requête contenant un point-virgule (dans une chaîne, un nom ou un commentaire) est donc refusée, pour qu’aucun découpage ne puisse y trouver une seconde instruction.

Captures d’écran

Explain Analyze, avec l’étape lente sélectionnée et un insight à son sujet :

Plan analysé avec un scan séquentiel sélectionné et son insight

Le même plan en tableau, trié par temps propre :

Vue tableau triée par temps propre

Une requête avec une CTE, un InitPlan et un SubPlan, chacun affiché dans sa propre branche :

Plan avec des branches CTE, InitPlan et SubPlan

Le plugin suit le thème de Beekeeper Studio :

Un plan avec le thème clair

Développement

Nécessite Node.js 22+ et Yarn 1.

yarn install
yarn dev        # builds with hot reload and links the plugin into Beekeeper Studio
yarn test       # unit tests
yarn build      # type-check and build into dist/

yarn dev utilise @beekeeperstudio/vite-plugin, qui crée un lien symbolique de ce dossier dans le répertoire des plugins de Beekeeper Studio.

Par défaut, Beekeeper Studio ne charge que ses propres plugins autorisés. Pour charger celui-ci, ajoute ce qui suit à ton fichier de configuration utilisateur et redémarre l’app :

[pluginSystem]
disabled = false

Connecte-toi ensuite à une base de données : Visualize Explain Plan apparaît dans le menu contextuel de l’éditeur de requêtes et Explain Visualizer dans le menu nouvel onglet.

Harness

dev/harness.html remplace Beekeeper Studio : il charge le plugin dans une iframe et répond à ses requêtes avec les mêmes messages que l’app, en exécutant les requêtes sur de vraies bases. C’est le moyen le plus rapide de travailler sur l’UI, d’essayer toutes les façons d’ouvrir l’onglet, et de changer de base ou de thème.

docker run -d --name pg -e POSTGRES_PASSWORD=pw -p 5432:5432 postgres:17
psql postgres://postgres:pw@localhost:5432/postgres -f scripts/fixtures/seed.sql

docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=pw -e MYSQL_DATABASE=shop -p 3306:3306 mysql:8.4
mysql -h127.0.0.1 -uroot -ppw shop < scripts/fixtures/seed-mysql.sql

PG_URL=postgres://postgres:pw@localhost:5432/postgres \
MYSQL_URL=mysql://root:pw@127.0.0.1:3306/shop \
yarn harness

MARIADB_URL fait pointer le MariaDB du harness vers un autre serveur (sinon il utilise MYSQL_URL). SQLite n’a besoin de rien : une base temporaire est remplie à partir de scripts/fixtures/seed-sqlite.sql à la première utilisation, ou SQLITE_PATH en désigne une.

Tests

  • tests/postgres parse de vrais plans capturés sur PostgreSQL 12, 17 et 18 (tests/fixtures/postgres). Chaque fixture contient le même plan en JSON et en texte, et les tests vérifient que les deux donnent le même arbre.
  • tests/mysql parse des plans capturés sur MySQL 5.7, 8.0.17, 8.0, 8.4 et 9.7 et MariaDB 10.4, 10.6 et 11.4 (tests/fixtures/mysql). Chaque fixture contient tous les formats disponibles sur son serveur pour une requête : arbre, EXPLAIN ANALYZE, les deux versions JSON, l’ANALYZE de MariaDB et le tableau classique. Les tests vérifient que les formats concordent.
  • tests/sqlite parse les lignes EXPLAIN QUERY PLAN de SQLite 3.16 (le format antérieur à 3.24), 3.27 et 3.45 (tests/fixtures/sqlite).
  • tests/integration exécute EXPLAIN sur des serveurs réels : PostgreSQL si PG_URL est défini, MySQL ou MariaDB si MYSQL_URL est défini, et SQLite toujours, via le module intégré node:sqlite de Node.
  • tests/state couvre le comportement de l’onglet avec un hôte factice.

Pour capturer des fixtures sur une autre version de serveur, remplis-la puis lance son script de capture :

# PostgreSQL: seed with scripts/fixtures/seed.sql
PG_URL=postgres://... FIXTURE_PREFIX=pg16- node scripts/fixtures/capture-postgres.mjs
# MySQL or MariaDB: seed with scripts/fixtures/seed-mysql.sql; named after the server
MYSQL_URL=mysql://root:pw@127.0.0.1:3306/shop node scripts/fixtures/capture-mysql.mjs
# SQLite: uses Python's bundled SQLite, so older Python images give older SQLite
python3 scripts/fixtures/capture-sqlite.py
docker run --rm -v "$PWD:/w" -w /w python:3.6-stretch python3 scripts/fixtures/capture-sqlite.py

Comment ça marche

src/
  core/        database-neutral plan model, analysis, metrics, layout, formatting
  engines/     one adapter per database family, plus the registry
    postgres/  EXPLAIN builder, JSON and text parsers, timing rules, insights
    mysql/     version detection; tree, JSON (both versions) and table readers; insights
    sqlite/    EXPLAIN QUERY PLAN rows and shell text; insights
    shared/    helpers adapters build on: a configurable SQL lexer, statement
               checks, Explain Analyze policies, result readers, a JSON parser
               that keeps repeated keys
  host/        everything that talks to Beekeeper Studio
  state/       the tab's state and actions
  components/  Vue components: diagram, table, details, raw view

Chaque adaptateur convertit la sortie EXPLAIN de sa base dans le modèle Plan de src/core/plan.ts. Un PlanNode a un nom d’opération, un kind (scan, index, join, filter, sort…), des estimations optionnelles (totalCost, estimatedRows), des mesures optionnelles (actualRows, loops, temps), des valeurs dérivées inclusiveTime, exclusiveTime et exclusiveCost, ainsi que des conditions et des propriétés groupées pour le panneau de détails. Chaque métrique est optionnelle et l’UI n’affiche que ce que le moteur fournit : un plan SQLite n’a aucun chiffre, donc le tableau montre les détails de chaque étape au lieu des coûts.

Le temps propre dépend du moteur, c’est donc l’adaptateur qui le calcule. Les règles de PostgreSQL sont dans src/engines/postgres/timing.ts : les temps par boucle sont multipliés par le nombre de boucles, les workers parallèles comptent une seule fois, le temps d’une CTE est imputé aux scans de CTE qui la lisent, et celui d’un InitPlan au nœud qui l’évalue. MySQL donne aussi des temps par boucle et utilise le computeLoopTimings partagé (src/core/metrics.ts), en ajoutant au temps de la requête les sous-requêtes de la liste select, qu’il n’inclut pas. MariaDB mesure les tables et les requêtes entières, pas les étapes ; computeMixedTimings complète le reste.

Les trois formats de MySQL sont lus avec les mêmes noms d’opération : le texte en arbre et le JSON version 2 listent les mêmes itérateurs, et le JSON par bloc de requête (ainsi que le tableau classique, reconstruit dans ce format) devient une chaîne de jointures dans l’ordre des tables. Les règles d’insights s’appuient sur ces noms, pas sur la sortie native.

src/core/analysis.ts ajoute ce que tous les moteurs ont en commun : parts de temps et de coût, étapes les plus lentes et insights sur les erreurs d’estimation de lignes.

Ajouter une base de données

Rien à modifier en dehors du dossier du moteur et du registre.

  1. Crée src/engines/<name>/ avec un objet qui implémente ExplainEngine (src/engines/types.ts):
    • connectionTypes : les types de connexion Beekeeper Studio qu’il gère, et products s’il en couvre plusieurs (MySQL et MariaDB).
    • inspect(sql, server) : découpe et classe la requête (modifie-t-elle des données ?). src/engines/shared/lexer.ts prend un dialecte : styles de commentaires, guillemets, placeholders.
    • analyzePolicy(statement, context) : si Explain Analyze peut l’exécuter, quoi demander d’abord et quoi afficher sous l’éditeur. src/engines/shared/analyzePolicy.ts contient les politiques rollback et lecture seule.
    • explain(request, runQuery) : envoie le SQL dont la base a besoin et parse le résultat. Il peut appeler runQuery plusieurs fois, par exemple pour se rabattre sur un format plus ancien.
    • loadServerInfo(runQuery) : la version, et tout ce que le moteur doit savoir d’autre (le mode SQL de MySQL). Étends ServerInfo avec tes propres champs.
    • canParse / parse : lit la sortie collée et les valeurs de la grille de résultats. Le texte brut renvoyé par un parse doit redonner le même plan une fois reparsé : les onglets restaurés sont reconstruits à partir de lui.
    • options : les options affichées dans le menu Options, chacune avec unavailable(server) quand elle nécessite un serveur plus récent.
  2. Associe les opérateurs natifs à des NodeKind et renseigne les métriques disponibles. Utilise les helpers de src/core/metrics.ts pour les coûts et les temps quand ils conviennent, et appelle finishPlan() de src/core/analysis.ts à la fin.
  3. Enregistre-le dans src/engines/registry.ts, ajoute des fixtures capturées sur de vrais serveurs (un script de capture dans scripts/fixtures/) et des tests comme les autres.

Notes pour SQL Server, qui est prévu :

  • Le client SQL Server de Beekeeper Studio découpe une requête sur ; et peut exécuter chaque instruction sur une connexion différente du pool. SET SHOWPLAN_XML ON doit être seul dans son batch et s’applique au batch suivant sur la même connexion : un plan estimé nécessite donc une nouvelle API hôte qui exécute les batchs sur une seule connexion.
  • SET STATISTICS XML ON avec la requête dans un seul batch (sans ;) donne le plan réel, mais exécute la requête et renvoie ses lignes au plugin.
  • Le XML de plan collé depuis SSMS (.sqlplan) ne nécessite aucun support de l’hôte.

Publication

  1. Lance bin/make-release.sh. Il propose la version suivante d’après les tags existants, définit version dans manifest.json et package.json, fait un commit et pousse un tag correspondant comme v0.1.0.
  2. Le workflow de release build, teste et crée une release GitHub en brouillon avec manifest.json et explain-visualizer-<version>.zip. Publie le brouillon.

yarn build && yarn package crée la même archive en local.

Licence

GPL-3.0. Voir LICENSE.