menu
languageDeutsch
save_alt Download

Explain Visualizer

Stell Ausführungspläne von Abfragen als interaktives Diagramm dar und finde die langsamen Teile einer Abfrage.

von rathboma Community-Plugin

Readme

Dieser Text stammt von Dritten und wurde automatisch übersetzt. Original auf Englisch lesen

Ein Beekeeper Studio-Plugin, das Ausführungspläne von Abfragen in ein interaktives Diagramm verwandelt, damit du den langsamen Teil einer Abfrage leicht findest.

Walkthrough: Rechtsklick auf eine Abfrage, Plan öffnen, Explain Analyze ausführen und zum langsamen Schritt springen

  • Diagramm des Plans, eingefärbt nach dem Anteil jedes Schritts an der Ausführungszeit (oder den geschätzten Kosten). Breitere Verbindungen transportieren mehr Zeilen.
  • Tabellenansicht im Stil von explain.depesz.com, sortierbar nach Eigenzeit.
  • Details zu jedem Schritt: Eigen- und Gesamtzeit, tatsächliche vs. geschätzte Zeilen, Loops, Kosten, Bedingungen, Buffer, Speicher- und Festplattennutzung, Worker.
  • Insights, die auf typische Probleme hinweisen: Full Scans, die die meisten Zeilen verwerfen, Tabellen, die pro Zeile eines Joins einmal gescannt werden, Sortierungen und Hashes, die auf die Festplatte auslagern, abhängige Subqueries, temporäre Indizes, die SQLite bei jedem Lauf neu baut, große Fehlschätzungen der Zeilenzahl und mehr.
  • Rohausgabe des Plans, bereit zum Kopieren oder Speichern.

Mehr dazu in den Screenshots unten.

Unterstützte Datenbanken

Datenbank Explain Explain Analyze Plan einfügen
PostgreSQL Ja Ja JSON und psql-Text
MySQL Ja 8.0.18+, lesende Anweisungen Tree, JSON (beide Versionen) und die klassische Tabelle
MariaDB Ja 10.1+, lesende Anweisungen JSON und die klassische Tabelle
SQLite, LibSQL Ja Nein: SQLite liefert keine Zeiten pro Schritt Der Baum aus der sqlite3-Shell oder die Ergebniszeilen
SQL Server Geplant    

Getestet mit PostgreSQL 12, 17 und 18; MySQL 5.7, 8.0.17, 8.0, 8.4 und 9.7; MariaDB 10.4, 10.6 und 11.4; sowie SQLite 3.16, 3.27 und 3.45. Das Plugin basiert auf einem datenbankneutralen Planmodell, sodass jede Datenbank nur einen Adapter braucht (siehe Eine Datenbank hinzufügen).

So nutzt du es

Es gibt mehrere Wege:

  • Abfrage-Editor: Klicke mit rechts auf eine Abfrage und wähle Visualize Explain Plan. Verwendet wird der markierte Text oder die Anweisung unter dem Cursor.
  • Menü „Neuer Tab“: Öffne Explain Visualizer, schreib eine Anweisung und wähle Explain oder Explain Analyze.
  • Ergebnistabelle: wenn du EXPLAIN selbst ausgeführt hast, klicke mit rechts auf den Plan und wähle Visualize Explain Plan:
    • ein Plan in einer Zelle (FORMAT JSON von PostgreSQL, Tree oder JSON von MySQL): Rechtsklick auf die Zelle;
    • ein Plan über mehrere Zeilen (Textausgabe von PostgreSQL): Rechtsklick auf den Spaltenkopf QUERY PLAN;
    • eine Tabelle (klassisches EXPLAIN von MySQL, EXPLAIN QUERY PLAN von SQLite): Markiere alle Zeilen und Spalten und klicke dann mit rechts auf die Auswahl. Bei SQLite reicht auch die detail-Spalte allein, dann aber ohne Verschachtelung.
  • Paste Plan: Füge EXPLAIN-Ausgabe ein, zum Beispiel aus psql, der mysql- oder sqlite3-Shell oder von Kollegen.

Das Kontextmenü des Abfrage-Editors mit Visualize Explain Plan

Explain und Explain Analyze

Explain fragt die Datenbank nach ihrem Plan, ohne die Anweisung auszuführen. Das ist immer sicher, deshalb läuft es automatisch, wenn der Tab aus dem Editor geöffnet wird.

Explain Analyze führt die Anweisung aus und misst jeden Schritt. Wie das sicher bleibt, hängt von der Datenbank ab (siehe unten). Beginnt der Editortext bereits mit EXPLAIN ANALYZE (oder dem Gegenstück der Datenbank), verwendet das Plugin ihn; lässt sich die Anweisung nicht analysieren, wird stattdessen Explain ausgeführt und der Grund unter dem Editor angezeigt.

PostgreSQL

Das Plugin sendet EXPLAIN ANALYZE ...; ROLLBACK; als eine einzige Anfrage, sodass beide Anweisungen eine implizite Transaktion teilen und alles, was die Anweisung geändert hat, zurückgerollt wird. Das gilt auch für einfache SELECTs, die schreibende Funktionen aufrufen können. Schlägt die Anweisung fehl, wird die Transaktion von selbst abgebrochen und die Pool-Verbindung bleibt sauber. Effekte außerhalb der Transaktion, etwa Sequenzwerte, werden nicht rückgängig gemacht.

Anweisungen, die Daten ändern (INSERT, UPDATE, DELETE, MERGE, datenändernde CTEs…), werden erst nach einer Bestätigung analysiert. Das Plugin liest die Anweisung mit einem Lexer, der PostgreSQLs eigenen Regeln für Strings, Kommentare und Dollar-Quotes folgt, und lehnt Eingaben mit mehr als einer Anweisung ab.

Bei schreibgeschützten Verbindungen erlaubt Beekeeper Studio kein ROLLBACK, daher können datenändernde Anweisungen nur mit Explain betrachtet werden, und Explain Analyze läuft nie automatisch. Ein SELECT, das eine schreibende Funktion aufruft, wird dort nicht erkannt – genau wie beim Ausführen in einem Abfrage-Tab.

Das Menü Options schaltet BUFFERS, VERBOSE, SETTINGS und WAL um. Optionen, die der Server nicht unterstützt, sind deaktiviert. Anweisungen mit $1-Parametern werden ab PostgreSQL 16 mit GENERIC_PLAN erklärt.

MySQL und MariaDB

Das Plugin liest die Version des Servers aus und verwendet das neueste verfügbare Planformat:

Server Explain Explain Analyze
MySQL 8.0.18+ EXPLAIN FORMAT=TREE EXPLAIN ANALYZE
MySQL 5.6 bis 8.0.17 EXPLAIN FORMAT=JSON Nicht verfügbar
MariaDB 10.1+ EXPLAIN FORMAT=JSON ANALYZE FORMAT=JSON
Ältere Server Die klassische EXPLAIN-Tabelle Nicht verfügbar

UPDATE und DELETE auf eine einzelne Tabelle haben keinen Tree; das Plugin weicht auf FORMAT=JSON aus und unter MySQL 9, dessen JSON sie ebenfalls nicht darstellen kann, auf die klassische Tabelle. TiDB wird erkannt und abgelehnt, statt falsch gelesen zu werden.

Explain Analyze führt nur lesende Anweisungen aus. Anders als bei PostgreSQL kann man sich bei MySQL nicht darauf verlassen, dass eine fehlgeschlagene Anfrage rückgängig gemacht wird: Als START TRANSACTION; ...; ROLLBACK; gesendet, würde eine fehlgeschlagene Anweisung die Transaktion auf einer Pool-Verbindung offen lassen, die danach andere Tabs nutzen. Ein SELECT, das eine schreibende Stored Function aufruft, committet, was die Funktion getan hat – genau wie in einem Abfrage-Tab. Unter MariaDB lehnt der schreibgeschützte Modus von Beekeeper Studio die ANALYZE-Anweisung ab, daher ist Explain Analyze bei schreibgeschützten Verbindungen nicht verfügbar.

Die Anweisung wird nach MySQLs Regeln gelesen: #- und -- -Kommentare, die erst bei einem Zeilenvorschub enden, Backslash-Escapes, "..."-Strings und /*! ... */-Kommentare, deren Inhalt MySQL ausführt. NO_BACKSLASH_ESCAPES und ANSI_QUOTES ändern, wo Strings enden, und eine Pool-Verbindung kann beides gesetzt haben – deshalb muss eine Anweisung unter jeder Kombination als eine einzige, lesende Anweisung gelten. Text, den ein Server auf mehr als eine Art lesen kann, wird abgelehnt statt erraten: ein versionsabhängiger /*!50700 ... */- oder ein reiner MariaDB-/*M! ... */-Kommentar, ein Wagenrücklauf in einem ---Kommentar oder ein NUL-Zeichen.

MariaDB misst die Zeit pro Tabelle statt pro Schritt, daher ist die Zeit eines Joins oder einer temporären Tabelle das, was die Abfrage über die gelesenen Tabellen hinaus gebraucht hat.

SQLite

EXPLAIN QUERY PLAN zeigt, welche Tabellen in welcher Reihenfolge und mit welchem Index gelesen werden. SQLite liefert keine Kosten, Zeilenzahlen oder Zeiten, daher zeigt das Diagramm die Schritte und die Tabellenansicht deren Details, und es gibt kein Explain Analyze. Insights weisen auf temporäre Indizes hin, die SQLite bei jedem Lauf neu baut, auf Tabellen, die pro Zeile eines Joins oder einer korrelierten Subquery einmal gescannt werden, und auf Sortierungen in einem temporären B-Tree. LibSQL-Verbindungen nutzen dieselbe Engine.

Beekeeper Studio zerlegt SQLite-Text vor dem Ausführen mit einem eigenen Parser in Anweisungen. Eine Anweisung mit einem Semikolon darin (in einem String, Namen oder Kommentar) wird daher abgelehnt: So kann kein Splitter darin eine zweite Anweisung finden.

Screenshots

Explain Analyze mit ausgewähltem langsamen Schritt und einem Insight dazu:

Analysierter Plan mit ausgewähltem Sequential Scan und dem zugehörigen Insight

Derselbe Plan als Tabelle, sortiert nach Eigenzeit:

Tabellenansicht, sortiert nach Eigenzeit

Eine Abfrage mit einer CTE, einem InitPlan und einem SubPlan, jeweils als eigener Zweig dargestellt:

Plan mit CTE-, InitPlan- und SubPlan-Zweigen

Das Plugin übernimmt das Theme von Beekeeper Studio:

Ein Plan im hellen Theme

Entwicklung

Benötigt Node.js 22+ und 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 nutzt @beekeeperstudio/vite-plugin, das diesen Ordner per Symlink in das Plugin-Verzeichnis von Beekeeper Studio einbindet.

Standardmäßig lädt Beekeeper Studio nur seine eigenen, freigegebenen Plugins. Um dieses zu laden, füge Folgendes zu deiner Benutzerkonfigurationsdatei hinzu und starte die App neu:

[pluginSystem]
disabled = false

Verbinde dich dann mit einer Datenbank: Visualize Explain Plan erscheint im Kontextmenü des Abfrage-Editors und Explain Visualizer im Menü „Neuer Tab“.

Harness

dev/harness.html ersetzt Beekeeper Studio: Es lädt das Plugin in einem iframe und beantwortet seine Anfragen mit denselben Nachrichten wie die App, wobei Abfragen gegen echte Datenbanken laufen. So arbeitest du am schnellsten an der UI, probierst jeden Weg aus, den Tab zu öffnen, und wechselst Datenbanken und Themes.

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 lässt die MariaDB des Harness auf einen anderen Server zeigen (sonst wird MYSQL_URL verwendet). SQLite braucht nichts: Beim ersten Einsatz wird eine temporäre Datenbank aus scripts/fixtures/seed-sqlite.sql befüllt, oder SQLITE_PATH gibt eine an.

Tests

  • tests/postgres parst echte Pläne aus PostgreSQL 12, 17 und 18 (tests/fixtures/postgres). Jede Fixture enthält denselben Plan als JSON und als Text, und die Tests prüfen, dass beide denselben Baum ergeben.
  • tests/mysql parst Pläne aus MySQL 5.7, 8.0.17, 8.0, 8.4 und 9.7 sowie MariaDB 10.4, 10.6 und 11.4 (tests/fixtures/mysql). Jede Fixture enthält für eine Anweisung jedes Format, das ihr Server bietet: Tree, EXPLAIN ANALYZE, beide JSON-Versionen, MariaDBs ANALYZE und die klassische Tabelle. Die Tests prüfen, dass die Formate übereinstimmen.
  • tests/sqlite parst EXPLAIN QUERY PLAN-Zeilen aus SQLite 3.16 (das Format vor 3.24), 3.27 und 3.45 (tests/fixtures/sqlite).
  • tests/integration führt EXPLAIN gegen laufende Server aus: PostgreSQL, wenn PG_URL gesetzt ist, MySQL oder MariaDB, wenn MYSQL_URL gesetzt ist, und SQLite immer, über das in Node eingebaute node:sqlite.
  • tests/state deckt das Verhalten des Tabs mit einem Fake-Host ab.

Um Fixtures von einer anderen Serverversion aufzuzeichnen, befülle sie mit Testdaten und führe ihr Capture-Skript aus:

# 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

So funktioniert es

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

Jeder Adapter wandelt die EXPLAIN-Ausgabe seiner Datenbank in das Plan-Modell in src/core/plan.ts um. Ein PlanNode hat einen Operationsnamen, einen kind (Scan, Index, Join, Filter, Sortierung…), optionale Schätzungen (totalCost, estimatedRows), optionale Messwerte (actualRows, loops, Zeiten), abgeleitete inclusiveTime, exclusiveTime und exclusiveCost sowie Bedingungen und gruppierte Eigenschaften für das Detail-Panel. Jede Metrik ist optional, und die UI zeigt nur, was eine Engine liefert: Ein SQLite-Plan hat keine Zahlen, daher zeigt die Tabelle statt Kosten die Details jedes Schritts.

Die Eigenzeit ist Engine-spezifisch, deshalb berechnet sie der Adapter. Die Regeln für PostgreSQL stehen in src/engines/postgres/timing.ts: Zeiten pro Loop werden mit der Anzahl der Loops multipliziert, parallele Worker werden einmal gezählt, CTE-Zeit wird den CTE-Scans zugerechnet, die sie lesen, und die Zeit eines InitPlans dem Knoten, der ihn auswertet. MySQL meldet Zeiten ebenfalls pro Loop und nutzt das gemeinsame computeLoopTimings (src/core/metrics.ts), wobei Subqueries in der Select-Liste zur Abfrage hinzugerechnet werden, da sie darin nicht enthalten sind. MariaDB misst Tabellen und ganze Abfragen, nicht Schritte; computeMixedTimings ergänzt den Rest.

Die drei Formate von MySQL werden auf dieselben Operationsnamen abgebildet: Tree-Text und JSON-Version 2 listen dieselben Iteratoren, und das Query-Block-JSON (sowie die klassische Tabelle, die darin umgebaut wird) wird zu einer Kette von Joins in der Reihenfolge der Tabellen. Die Insight-Regeln arbeiten mit diesen Namen, nicht mit der nativen Ausgabe.

src/core/analysis.ts ergänzt, was alle Engines gemeinsam haben: Zeit- und Kostenanteile, die langsamsten Schritte und Insights zu Fehlschätzungen der Zeilenzahl.

Eine Datenbank hinzufügen

Außerhalb des Engine-Ordners und der Registry muss nichts geändert werden.

  1. Erstelle src/engines/<name>/ mit einem Objekt vom Typ ExplainEngine (src/engines/types.ts):
    • connectionTypes: die Verbindungstypen von Beekeeper Studio, die sie unterstützt, und products, wenn sie mehrere abdeckt (MySQL und MariaDB).
    • inspect(sql, server): die Anweisung zerlegen und klassifizieren (ändert sie Daten?). src/engines/shared/lexer.ts nimmt einen Dialekt entgegen: Kommentarstile, Quotes, Platzhalter.
    • analyzePolicy(statement, context): ob Explain Analyze sie ausführen darf, was vorher abgefragt wird und was unter dem Editor steht. src/engines/shared/analyzePolicy.ts enthält die Rollback- und die Nur-Lesen-Policy.
    • explain(request, runQuery): das SQL senden, das die Datenbank braucht, und das Ergebnis parsen. runQuery darf mehrfach aufgerufen werden, etwa um auf ein älteres Format auszuweichen.
    • loadServerInfo(runQuery): die Version und alles, was die Engine sonst noch wissen muss (bei MySQL den SQL-Modus). Erweitere ServerInfo um eigene Felder.
    • canParse / parse: eingefügte Ausgabe und Werte aus der Ergebnistabelle lesen. Der Rohtext, den ein Parse zurückgibt, muss sich wieder in denselben Plan parsen lassen: Wiederhergestellte Tabs werden daraus neu aufgebaut.
    • options: Schalter im Menü Options, jeweils mit unavailable(server), falls ein neuerer Server nötig ist.
  2. Ordne native Operatoren NodeKinds zu und fülle die vorhandenen Metriken aus. Nutze für Kosten und Zeiten die Helfer in src/core/metrics.ts, wo sie passen, und rufe am Ende finishPlan() aus src/core/analysis.ts auf.
  3. Registriere sie in src/engines/registry.ts, füge Fixtures von echten Servern hinzu (ein Capture-Skript in scripts/fixtures/) und schreib Tests wie bei den anderen.

Hinweise zu SQL Server, das geplant ist:

  • Der SQL-Server-Client von Beekeeper Studio teilt eine Anfrage bei ; auf und kann jede Anweisung auf einer anderen Pool-Verbindung ausführen. SET SHOWPLAN_XML ON muss allein in seinem Batch stehen und gilt für den nächsten Batch auf derselben Verbindung, daher braucht ein geschätzter Plan eine neue Host-API, die Batches auf einer Verbindung ausführt.
  • SET STATISTICS XML ON mit der Anweisung in einem Batch (ohne ;) liefert den tatsächlichen Plan, führt die Anweisung aber aus und gibt ihre Zeilen an das Plugin zurück.
  • Aus SSMS eingefügtes Plan-XML (.sqlplan) braucht keine Host-Unterstützung.

Release

  1. Führe bin/make-release.sh aus. Es schlägt anhand der vorhandenen Tags die nächste Version vor, setzt version in manifest.json und package.json, committet und pusht einen passenden Tag wie v0.1.0.
  2. Der Release-Workflow baut, testet und erstellt einen GitHub-Release-Entwurf mit manifest.json und explain-visualizer-<version>.zip. Veröffentliche den Entwurf.

yarn build && yarn package erstellt dasselbe Archiv lokal.

Lizenz

GPL-3.0. Siehe LICENSE.