Um plugin do Beekeeper Studio que transforma planos de execução de consultas em um diagrama interativo, para que a parte lenta de uma consulta seja fácil de encontrar.

- Diagrama do plano, colorido pela parcela do tempo de execução (ou custo estimado) de cada etapa. Ligações mais largas carregam mais linhas.
- Visualização em tabela no estilo do
explain.depesz.com, ordenável pelo tempo próprio. - Detalhes de cada etapa: tempo próprio e total, linhas reais vs. estimadas, loops, custos, condições, buffers, uso de memória e disco, workers.
- Insights que apontam problemas comuns: scans completos que descartam a maioria das linhas, tabelas lidas uma vez por linha de um join, sorts e hashes que transbordam para o disco, subconsultas dependentes, índices temporários que o SQLite cria a cada execução, grandes erros de estimativa de linhas e mais.
- Saída bruta do plano, pronta para copiar ou salvar.
Veja mais nas capturas de tela abaixo.
Bancos de dados suportados
| Banco de dados | Explain | Explain Analyze | Colar um plano |
|---|---|---|---|
| PostgreSQL | Sim | Sim | JSON e texto do psql |
| MySQL | Sim | 8.0.18+, instruções de leitura | Árvore, JSON (ambas as versões) e a tabela clássica |
| MariaDB | Sim | 10.1+, instruções de leitura | JSON e a tabela clássica |
| SQLite, LibSQL | Sim | Não: o SQLite não tem tempos por etapa | A árvore do shell sqlite3 ou as linhas de resultado |
| SQL Server | Planejado |
Testado com PostgreSQL 12, 17 e 18; MySQL 5.7, 8.0.17, 8.0, 8.4 e 9.7; MariaDB 10.4, 10.6 e 11.4; e SQLite 3.16, 3.27 e 3.45. O plugin é construído em torno de um modelo de plano neutro em relação ao banco de dados, então cada banco só precisa de um adaptador (veja Adicionando um banco de dados).
Como usar
Há várias formas de começar:
- Editor de consultas: clique com o botão direito em uma consulta e escolha Visualize Explain Plan. É usado o texto selecionado ou a instrução sob o cursor.
- Menu de nova aba: abra o Explain Visualizer, escreva uma instrução e escolha Explain ou Explain Analyze.
- Grade de resultados: depois de executar EXPLAIN você mesmo, clique com o botão direito no plano e escolha Visualize Explain Plan:
- um plano em uma célula (
FORMAT JSONdo PostgreSQL, árvore ou JSON do MySQL): clique com o botão direito na célula; - um plano distribuído em linhas (saída de texto do PostgreSQL): clique com o botão direito no cabeçalho da coluna
QUERY PLAN; - uma tabela (
EXPLAINclássico do MySQL,EXPLAIN QUERY PLANdo SQLite): selecione todas as linhas e colunas e clique com o botão direito na seleção. Só a coluna de detalhes também funciona no SQLite, mas sem o aninhamento.
- um plano em uma célula (
- Paste Plan: cole a saída do EXPLAIN, por exemplo do psql, do shell mysql ou sqlite3, ou de um colega.

Explain e Explain Analyze
Explain pede ao banco de dados o seu plano sem executar a instrução. É sempre seguro, por isso é executado automaticamente quando a aba é aberta a partir do editor.
Explain Analyze executa a instrução e mede cada etapa. Como isso é mantido seguro depende do banco de dados, conforme abaixo. Se o texto do editor já começa com EXPLAIN ANALYZE (ou o equivalente do banco), o plugin o utiliza; quando a instrução não pode ser analisada, é feito apenas o explain, e o motivo aparece abaixo do editor.
PostgreSQL
O plugin envia EXPLAIN ANALYZE ...; ROLLBACK; como uma única requisição, então as duas instruções compartilham uma transação implícita e tudo o que a instrução alterou é revertido. Isso inclui SELECTs simples, que podem chamar funções que escrevem dados. Se a instrução falhar, a transação é abortada sozinha e a conexão do pool fica limpa. Efeitos fora da transação, como valores de sequences, não são desfeitos.
Instruções que alteram dados (INSERT, UPDATE, DELETE, MERGE, CTEs que modificam dados…) só são analisadas após uma confirmação. O plugin lê a instrução com um lexer que segue as próprias regras do PostgreSQL para strings, comentários e dollar quotes, e recusa entradas com mais de uma instrução.
Em conexões somente leitura, o Beekeeper Studio não permite o ROLLBACK, então instruções que alteram dados só podem passar por explain, e o Explain Analyze nunca é executado automaticamente. Um SELECT que chama uma função que escreve dados não é detectado ali, assim como ao executá-lo em uma aba de consulta.
O menu Options ativa ou desativa BUFFERS, VERBOSE, SETTINGS e WAL. Opções que o servidor não suporta ficam desabilitadas. Instruções com parâmetros $1 são explicadas com GENERIC_PLAN no PostgreSQL 16+.
MySQL e MariaDB
O plugin lê a versão do servidor e usa o formato de plano mais recente disponível:
| Servidor | Explain | Explain Analyze |
|---|---|---|
| MySQL 8.0.18+ | EXPLAIN FORMAT=TREE |
EXPLAIN ANALYZE |
| MySQL 5.6 a 8.0.17 | EXPLAIN FORMAT=JSON |
Não disponível |
| MariaDB 10.1+ | EXPLAIN FORMAT=JSON |
ANALYZE FORMAT=JSON |
| Servidores mais antigos | A tabela clássica do EXPLAIN |
Não disponível |
UPDATE e DELETE de tabela única não têm árvore; o plugin recorre ao FORMAT=JSON e, no MySQL 9, cujo JSON também não consegue mostrá-los, à tabela clássica. O TiDB é reconhecido e recusado em vez de ser lido incorretamente.
O Explain Analyze só executa instruções de leitura. Diferente do PostgreSQL, não dá para confiar que o MySQL desfaça uma requisição que falhou: enviada como START TRANSACTION; ...; ROLLBACK;, uma instrução que falhasse deixaria a transação aberta em uma conexão do pool usada depois por outras abas. Um SELECT que chama uma stored function que escreve dados confirma o que a função fez, assim como ao executá-lo em uma aba de consulta. No MariaDB, o modo somente leitura do Beekeeper Studio recusa a instrução ANALYZE, então o Explain Analyze não está disponível em conexões somente leitura.
A instrução é lida com as regras do MySQL: comentários # e -- que só terminam em uma quebra de linha (line feed), escapes com barra invertida, strings "..." e comentários /*! ... */, cujo conteúdo o MySQL executa. NO_BACKSLASH_ESCAPES e ANSI_QUOTES mudam onde as strings terminam, e uma conexão do pool pode ter qualquer um deles, então a instrução precisa ser lida como uma única instrução, e de leitura, em todas as combinações. Texto que um servidor possa ler de mais de uma forma é recusado em vez de adivinhado: um comentário condicionado à versão /*!50700 ... */ ou exclusivo do MariaDB /*M! ... */, um carriage return dentro de um comentário -- ou um caractere NUL.
O MariaDB mede o tempo de cada tabela em vez de cada etapa, então o tempo de um join ou de uma tabela temporária é o que a consulta levou além das tabelas que leu.
SQLite
EXPLAIN QUERY PLAN mostra quais tabelas são lidas, em que ordem e com qual índice. O SQLite não informa custos, contagens de linhas nem tempos, então o diagrama mostra as etapas e a visualização em tabela os seus detalhes, e não há Explain Analyze. Os insights apontam índices temporários que o SQLite cria a cada execução, tabelas lidas uma vez por linha de um join ou de uma subconsulta correlacionada e sorts em uma B-tree temporária. Conexões LibSQL usam o mesmo motor.
O Beekeeper Studio divide o texto SQLite em instruções com seu próprio parser antes de executá-las, então uma instrução com ponto e vírgula dentro dela (em uma string, nome ou comentário) é recusada: assim nenhum divisor consegue encontrar uma segunda instrução nela.
Capturas de tela
Explain Analyze, com a etapa lenta selecionada e um insight sobre ela:

O mesmo plano como tabela, ordenado pelo tempo próprio:

Uma consulta com uma CTE, um InitPlan e um SubPlan, cada um exibido como um ramo próprio:

O plugin segue o tema do Beekeeper Studio:

Desenvolvimento
Requer Node.js 22+ e 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 usa o @beekeeperstudio/vite-plugin, que cria um symlink desta pasta no diretório de plugins do Beekeeper Studio.
Por padrão, o Beekeeper Studio só carrega os próprios plugins da lista de permitidos. Para carregar este, adicione o seguinte ao seu arquivo de configuração do usuário e reinicie o app:
[pluginSystem]
disabled = false
Depois conecte-se a um banco de dados: Visualize Explain Plan aparece no menu de contexto do editor de consultas e Explain Visualizer no menu de nova aba.
Harness
dev/harness.html substitui o Beekeeper Studio: carrega o plugin em um iframe e responde às suas requisições com as mesmas mensagens que o app usa, executando consultas em bancos de dados reais. É a forma mais rápida de trabalhar na UI, testar todas as formas de abrir a aba, trocar de banco de dados e de tema.
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 aponta o MariaDB do harness para outro servidor (caso contrário, ele usa MYSQL_URL). O SQLite não precisa de nada: um banco temporário é populado a partir de scripts/fixtures/seed-sqlite.sql no primeiro uso, ou SQLITE_PATH indica um.
Testes
tests/postgresfaz o parse de planos reais capturados do PostgreSQL 12, 17 e 18 (tests/fixtures/postgres). Cada fixture tem o mesmo plano em JSON e em texto, e os testes verificam se ambos produzem a mesma árvore.tests/mysqlfaz o parse de planos capturados do MySQL 5.7, 8.0.17, 8.0, 8.4 e 9.7 e do MariaDB 10.4, 10.6 e 11.4 (tests/fixtures/mysql). Cada fixture tem todos os formatos que o servidor oferece para uma instrução: árvore,EXPLAIN ANALYZE, ambas as versões de JSON, oANALYZEdo MariaDB e a tabela clássica. Os testes verificam se os formatos concordam.tests/sqlitefaz o parse de linhas deEXPLAIN QUERY PLANdo SQLite 3.16 (o formato anterior ao 3.24), 3.27 e 3.45 (tests/fixtures/sqlite).tests/integrationexecuta EXPLAIN em servidores ativos: PostgreSQL quandoPG_URLestá definido, MySQL ou MariaDB quandoMYSQL_URLestá definido, e SQLite sempre, via onode:sqlitenativo do Node.tests/statecobre o comportamento da aba com um host falso.
Para capturar fixtures de outra versão de servidor, popule-o e execute o script de captura correspondente:
# 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
Como funciona
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
Cada adaptador converte a saída do EXPLAIN do seu banco de dados para o modelo Plan em src/core/plan.ts. Um PlanNode tem um nome de operação, um kind (scan, index, join, filter, sort…), estimativas opcionais (totalCost, estimatedRows), medições opcionais (actualRows, loops, tempos), os derivados inclusiveTime, exclusiveTime e exclusiveCost, além de condições e propriedades agrupadas para o painel de detalhes. Toda métrica é opcional, e a UI só mostra o que o motor fornece: um plano do SQLite não tem números, então a tabela mostra os detalhes de cada etapa em vez de custos.
O tempo próprio é específico de cada motor, então o adaptador o calcula. As regras do PostgreSQL ficam em src/engines/postgres/timing.ts: tempos por loop são multiplicados pelos loops, workers paralelos são contados uma vez, o tempo de uma CTE é atribuído aos scans de CTE que a leem, e o tempo de um InitPlan é atribuído ao nó que o avalia. O MySQL também informa tempos por loop e usa o computeLoopTimings compartilhado (src/core/metrics.ts), com as subconsultas da lista do select somadas à consulta, que não as inclui. O MariaDB mede tabelas e consultas inteiras, não etapas; o computeMixedTimings completa o resto.
Os três formatos do MySQL são lidos com os mesmos nomes de operação: o texto em árvore e o JSON versão 2 listam os mesmos iteradores, e o JSON por query block (e a tabela clássica, reconstruída nele) vira uma cadeia de joins na ordem das tabelas. As regras de insights trabalham com esses nomes, não com a saída nativa.
src/core/analysis.ts adiciona o que todos os motores compartilham: parcelas de tempo e custo, as etapas mais lentas e insights de erro de estimativa de linhas.
Adicionando um banco de dados
Nada fora da pasta do motor e do registro precisa mudar.
- Crie
src/engines/<name>/com um objeto que implementeExplainEngine(src/engines/types.ts):connectionTypes: os tipos de conexão do Beekeeper Studio que ele trata, eproductsquando cobre vários (MySQL e MariaDB).inspect(sql, server): divide e classifica a instrução (ela altera dados?).src/engines/shared/lexer.tsrecebe um dialeto: estilos de comentário, aspas, placeholders.analyzePolicy(statement, context): se o Explain Analyze pode executá-la, o que perguntar antes e o que exibir abaixo do editor.src/engines/shared/analyzePolicy.tstem as políticas de rollback e de somente leitura.explain(request, runQuery): envia o SQL que o banco precisar e faz o parse do resultado. Pode chamarrunQueryvárias vezes, por exemplo para recorrer a um formato mais antigo.loadServerInfo(runQuery): a versão e qualquer outra coisa que o motor precise saber (o SQL mode do MySQL). EstendaServerInfocom seus próprios campos.canParse/parse: lê saídas coladas e valores da grade de resultados. O texto bruto retornado por um parse deve gerar de volta o mesmo plano: abas restauradas são reconstruídas a partir dele.options: opções exibidas no menu Options, cada uma comunavailable(server)quando precisa de um servidor mais novo.
- Mapeie os operadores nativos para
NodeKinds e preencha as métricas que existirem. Use os helpers desrc/core/metrics.tspara custos e tempos quando couberem e chamefinishPlan()desrc/core/analysis.tsno final. - Registre-o em
src/engines/registry.ts, adicione fixtures capturadas de servidores reais (um script de captura emscripts/fixtures/) e testes como os demais.
Notas para o SQL Server, que está planejado:
- O cliente SQL Server do Beekeeper Studio divide uma requisição em
;e pode executar cada instrução em uma conexão diferente do pool.SET SHOWPLAN_XML ONprecisa estar sozinho no seu batch e se aplica ao próximo batch na mesma conexão, então um plano estimado exige uma nova API do host que execute batches em uma única conexão. SET STATISTICS XML ONcom a instrução em um único batch (sem;) fornece o plano real, mas executa a instrução e retorna suas linhas para o plugin.- XML de plano colado do SSMS (
.sqlplan) não precisa de suporte do host.
Publicando versões
- Execute
bin/make-release.sh. Ele sugere a próxima versão a partir das tags existentes, defineversionemmanifest.jsonepackage.json, faz o commit e envia uma tag correspondente, comov0.1.0. - O workflow de release faz o build, roda os testes e cria um rascunho de release no GitHub com
manifest.jsoneexplain-visualizer-<version>.zip. Publique o rascunho.
yarn build && yarn package cria o mesmo arquivo localmente.
Licença
GPL-3.0. Veja LICENSE.