O servidor está lento, os usuários reclamam, mas ninguém sabe qual consulta está causando o problema. Reiniciar resolve por alguns minutos e a causa volta. Quem trabalha com Firebird já viveu esse ciclo.
O Firebird expõe um conjunto de tabelas de monitoramento em memória — as MON$ — que mostram, em tempo real, quais conexões estão abertas, quais statements estão ativos e quantas páginas cada um leu. Com elas, dá para sair do "achismo" e apontar a consulta culpada.
Este guia mostra como consultar essas tabelas, interpretar as métricas e diferenciar três problemas que parecem iguais: query sem índice, query repetida demais e transação antiga segurando o banco.
O que são as tabelas MON$
As tabelas MON$ existem desde o Firebird 2.1. Elas não são tabelas físicas: são visões em memória do estado do servidor naquele instante. Não ocupam espaço no banco e não precisam de manutenção.
As principais são:
| Tabela | O que mostra |
|---|---|
MON$ATTACHMENTS |
conexões abertas (usuário, IP, processo, estado) |
MON$TRANSACTIONS |
transações ativas e o isolamento de cada uma |
MON$STATEMENTS |
comandos SQL em execução ou parados, com o texto |
MON$RECORD_STATS |
leituras indexadas e não indexadas por statement |
MON$PAGE_STATS |
páginas lidas, escritas e buscadas em cache |
MON$DATABASE |
idade das transações mais antigas do banco |
Por padrão, apenas o SYSDBA enxerga todas as linhas. Para usar em produção com outro usuário, conceda SELECT nas tabelas MON$ ao usuário de monitoramento.
Achando o que está rodando agora
Comece pelos statements ativos:
SELECT
s.MON$STATEMENT_ID,
s.MON$ATTACHMENT_ID,
s.MON$STATE,
s.MON$TIMESTAMP,
s.MON$SQL_TEXT
FROM MON$STATEMENTS s
WHERE s.MON$STATE = 1
ORDER BY s.MON$TIMESTAMP;
MON$STATE = 1 significa ativo; 0 é idle. Um statement ativo há muito tempo é o primeiro suspeito.
Para ligar o statement ao usuário e ao endereço de origem:
SELECT
a.MON$USER,
a.MON$REMOTE_ADDRESS,
a.MON$REMOTE_PROCESS,
s.MON$STATEMENT_ID,
s.MON$TIMESTAMP,
s.MON$SQL_TEXT
FROM MON$STATEMENTS s
JOIN MON$ATTACHMENTS a ON a.MON$ATTACHMENT_ID = s.MON$ATTACHMENT_ID
WHERE s.MON$STATE = 1;
Agora você sabe quem está rodando o quê.
Lendo as métricas que importam
O texto da query sozinho não diz se ela leu 10 linhas ou 10 milhões. As estatísticas dizem:
SELECT
s.MON$STATEMENT_ID,
s.MON$ATTACHMENT_ID,
s.MON$SQL_TEXT,
r.MON$RECORD_SEQ_READS,
r.MON$RECORD_IDX_READS,
p.MON$PAGE_FETCHES,
p.MON$PAGE_READS
FROM MON$STATEMENTS s
LEFT JOIN MON$RECORD_STATS r ON r.MON$STAT_ID = s.MON$STAT_ID
LEFT JOIN MON$PAGE_STATS p ON p.MON$STAT_ID = s.MON$STAT_ID
WHERE s.MON$STATE = 1;
| Métrica | Significado | O que indica |
|---|---|---|
MON$RECORD_SEQ_READS |
leituras sequenciais de registros | varredura sem índice |
MON$RECORD_IDX_READS |
leituras via índice | índice sendo usado |
MON$PAGE_FETCHES |
páginas buscadas (cache + disco) | volume de dados lido |
MON$PAGE_READS |
páginas lidas do disco | cache frio ou tabela grande |
MON$PAGE_WRITES |
páginas gravadas | escrita pesada |
A leitura mais útil é a proporção entre sequencial e indexado. Muitas leituras sequenciais com poucas indexadas quase sempre significam um WHERE que o índice não cobre — coluna com função, tipo diferente ou índice inexistente.
Três problemas diferentes que parecem o mesmo
1. Query sem índice
Sintoma: MON$RECORD_SEQ_READS alto, MON$RECORD_IDX_READS baixo, MON$PAGE_FETCHES alto.
Ação: revisar o plano com SET PLAN ON, conferir a ordem das colunas e criar o índice adequado. Se a consulta usa função na coluna (UPPER, CAST), o índice comum não serve.
2. Query repetida demais (N+1)
Sintoma: muitos statements triviais, cada um lendo poucas linhas, todos com o mesmo texto e parâmetros diferentes.
Ação: não é índice, é padrão de acesso. Agrupe as consultas em um IN (...) ou JOIN. O volume de execuções é o problema, não o custo de cada uma.
3. Transação antiga segurando o banco
Sintoma: tudo fica lento sem uma query específica aparecer como culpada. O banco acumula versões antigas de registros e o garbage collection não consegue limpar.
Verifique a idade das transações:
SELECT
MON$OLDEST_TRANSACTION,
MON$OLDEST_ACTIVE,
MON$OLDEST_SNAPSHOT
FROM MON$DATABASE;
Se MON$OLDEST_ACTIVE está muito atrás do último ID de transação, existe uma transação aberta há muito tempo — às vezes uma conexão esquecida, às vezes um COMMIT que nunca chegou. Enquanto ela existir, o sweep não avança e as consultas pagam a conta.
Para localizar a transação:
SELECT
t.MON$TRANSACTION_ID,
t.MON$ATTACHMENT_ID,
t.MON$STATE,
t.MON$TIMESTAMP,
a.MON$USER,
a.MON$REMOTE_ADDRESS
FROM MON$TRANSACTIONS t
JOIN MON$ATTACHMENTS a ON a.MON$ATTACHMENT_ID = t.MON$ATTACHMENT_ID
ORDER BY t.MON$TIMESTAMP;
O que fazer quando o statement está travado
Um statement ativo há minutos pode estar esperando lock, I/O ou uma transação concorrente. Antes de cancelar:
- Confirme o tempo.
MON$TIMESTAMPmostra quando começou. - Veja a origem. Usuário e IP indicam se é tela, job ou integração.
- Verifique locks.
MON$RECORD_STATScom conflitos altos sugere disputa de registro. - Só então cancele.
DELETE FROM MON$STATEMENTS WHERE MON$STATEMENT_ID = ?interrompe o comando. É uma ação administrativa, não um teste.
Cancelar sem identificar a causa apenas transfere o problema para o próximo horário de pico.
Monitorando de forma contínua
Consultar as MON$ manualmente resolve o incidente de hoje. Para evitar o próximo:
- Registre periodicamente os statements com maior
MON$PAGE_FETCHESem uma tabela de histórico. - Alerte quando
MON$OLDEST_ACTIVEficar muito distante do topo. - Acompanhe o número de conexões ativas por usuário.
- Correlacione picos de CPU/RAM com o horário dos statements mais pesados.
Uma consulta a cada minuto em MON$STATEMENTS custa pouco e transforma "o banco travou" em "esta consulta rodou às 10h02".
Passo a passo para investigar
- Liste os statements ativos (
MON$STATE = 1). - Junte com
MON$ATTACHMENTSpara identificar usuário e origem. - Compare leituras sequenciais e indexadas.
- Verifique a idade das transações em
MON$DATABASE. - Classifique: índice, N+1 ou transação antiga.
- Corrija a causa e meça de novo com as mesmas métricas.
Checklist
- Sei quais statements estão ativos agora.
- Consigo ligar cada statement ao usuário e ao IP.
- Sei diferenciar leitura sequencial de leitura indexada.
- Verifico a transação mais antiga antes de culpar a query.
- Tenho um histórico, não só uma foto do momento.
- Só cancelo um statement depois de identificar a origem.
Conclusão
As tabelas MON$ são o raio-X do Firebird em produção. Elas mostram o que está rodando, quanto cada comando leu e se existe uma transação antiga impedindo a limpeza. Com esses três sinais, a investigação deixa de ser tentativa e erro.
Se você precisa transformar uma descrição em português na consulta Firebird certa — com os filtros e índices no lugar — o Sqlify gera o SQL e explica cada cláusula. Descreva o que quer consultar e revise a query pronta antes de colocar em produção.
Leia também: