Quando uma consulta começa a demorar, o primeiro reflexo é criar índice em tudo. Em Firebird, isso costuma piorar o cenário: cada índice extra aumenta o custo de INSERT, UPDATE e DELETE, e o otimizador pode escolher um plano ruim.
Índice não é um botão de "deixar rápido". É uma estrutura ordenada que o banco mantém em paralelo à tabela, e que só ajuda quando a consulta consegue aproveitar essa ordem. Escolher a coluna errada, ou a ordem errada dentro de um índice composto, é o motivo mais comum de uma query continuar lenta mesmo depois de "indexada".
Este guia mostra como o Firebird usa índices, como escolher as colunas, como ler o plano de execução e quando o melhor é não criar índice nenhum.
Como o Firebird usa um índice
O índice padrão do Firebird é uma árvore B (B-tree). Ele guarda os valores da coluna em ordem, junto com um ponteiro para a linha correspondente. Isso permite três operações baratas:
- busca por igualdade (
WHERE ID = ?), descendo direto até a chave; - busca por faixa (
BETWEEN,>,<), percorrendo um trecho contínuo; - ordenação (
ORDER BY), quando a ordem do índice coincide com a pedida.
O otimizador escolhe entre percorrer a tabela inteira (plano NATURAL) ou usar um índice. A decisão depende de estatísticas: quantas linhas a tabela tem, quantos valores distintos a coluna possui e quanto do índice será lido.
Se a consulta vai retornar 40% da tabela, ler o índice e depois buscar cada linha na tabela pode custar mais do que uma varredura sequencial. É por isso que índice em coluna de baixa seletividade raramente ajuda.
O problema: índice criado, query continua lenta
Considere uma tabela de apontamentos de horas com milhões de linhas:
SELECT IDPROFISSIONAL, DATA_APONTAMENTO, HORAS
FROM TIMECARDS
WHERE DATA_APONTAMENTO BETWEEN ? AND ?
AND IDPROFISSIONAL = ?;
Se existir apenas um índice em DATA_APONTAMENTO, o Firebird localiza o período e depois testa IDPROFISSIONAL linha a linha dentro dessa faixa. Em um mês com muitos profissionais, ainda é muita leitura.
O índice composto resolve o problema:
CREATE INDEX IDX_TIMECARDS_PROF_DATA
ON TIMECARDS (IDPROFISSIONAL, DATA_APONTAMENTO);
Agora o banco desce direto no profissional e percorre apenas o intervalo de datas daquele profissional.
A ordem das colunas em um índice composto
Esta é a parte que mais gente erra. Em um índice composto, o Firebird percorre as colunas da esquerda para a direita. A primeira coluna precisa ser útil para reduzir o conjunto; uma coluna de faixa no começo limita o uso das seguintes.
Regra prática:
- Colunas comparadas por igualdade (
=,IN) vêm primeiro. - A coluna de faixa (
BETWEEN,>,<) vem depois. - Colunas usadas apenas em
ORDER BYpodem entrar no fim, se ajudarem a evitar ordenação.
| Consulta | Índice recomendado |
|---|---|
WHERE IDPROFISSIONAL = ? AND DATA BETWEEN ? AND ? |
(IDPROFISSIONAL, DATA) |
WHERE IDCLIENTE = ? AND STATUS = ? AND DATA > ? |
(IDCLIENTE, STATUS, DATA) |
WHERE IDCLIENTE = ? ORDER BY DATA DESC |
(IDCLIENTE, DATA) |
WHERE STATUS = ? (2 valores possíveis) |
provavelmente nenhum |
Note a diferença entre (IDCLIENTE) e (IDCLIENTE, DATA): o segundo cobre buscas por cliente e por cliente + data. O primeiro pode ser redundante se o segundo existir.
Seletividade: o cálculo que muda a decisão
Seletividade é a proporção de linhas que uma coluna consegue distinguir. Quanto mais valores distintos, melhor.
Exemplo com 1.000.000 de linhas:
| Coluna | Valores distintos | Linhas por valor | Seletividade |
|---|---|---|---|
ATIVO (S/N) |
2 | ~500.000 | péssima |
STATUS (5 valores) |
5 | ~200.000 | ruim |
IDCLIENTE |
50.000 | ~20 | ótima |
ID (chave) |
1.000.000 | 1 | perfeita |
O Firebird mantém estatísticas de seletividade por índice. Se os dados mudaram muito desde a criação (carga inicial, importação, limpeza), vale recalcular:
SET STATISTICS INDEX IDX_TIMECARDS_PROF_DATA;
O recálculo não muda os dados; apenas atualiza a informação que o otimizador usa para escolher o plano.
Como ler o plano de execução
Antes de criar mais um índice, descubra o que o banco está fazendo hoje. No isql:
SET PLAN ON;
SELECT IDPROFISSIONAL, DATA_APONTAMENTO, HORAS
FROM TIMECARDS
WHERE IDPROFISSIONAL = 42
AND DATA_APONTAMENTO BETWEEN '2026-10-01' AND '2026-10-31';
SET PLAN OFF;
A saída mostra algo como:
PLAN (TIMECARDS INDEX (IDX_TIMECARDS_PROF_DATA))
Se aparecer PLAN (TIMECARDS NATURAL), o banco está varrendo a tabela inteira. Para ver o plano sem executar a consulta:
SET PLANONLY ON;
SELECT ...;
SET PLANONLY OFF;
Três leituras comuns:
INDEX (NOME_DO_INDICE)— o índice foi usado. Confirme se é o índice certo.NATURAL— varredura completa. Pode ser correto (tabela pequena ou retorno grande) ou sintoma de índice ausente/inutilizável.ORDER— houve ordenação em memória. Um índice adequado pode eliminar essa etapa.
Armadilhas que anulam o índice
Função na coluna do filtro —
WHERE UPPER(NOME) = ?não usa o índice deNOME, porque o valor indexado é o original, não o maiúsculo. Solução: índice calculado ou normalizar o dado na gravação.CREATE INDEX IDX_CLIENTES_NOME_UP ON CLIENTES COMPUTED BY (UPPER(NOME));Comparar tipos diferentes — string contra número força conversão e pode descartar o índice. Garanta que o bind envie o tipo correto (
INTEGERcomo inteiro,VARCHARcomo string).LIKE '%texto'— curinga no início impede a busca por faixa. O índice só ajuda emLIKE 'texto%'.ORentre colunas diferentes — muitas vezes vira varredura completa.UNION ALLde duas consultas indexadas pode ser mais rápido.Operação aritmética na coluna —
WHERE VALOR * 2 > 100não usa o índice deVALOR. Reescreva comoWHERE VALOR > 50.NOTe<>— em colunas de baixa seletividade, o banco prefere varrer a tabela. Índice raramente resolve.Índice em tabela pequena — para dezenas ou centenas de linhas, a varredura natural já é barata e o índice só adiciona custo de manutenção.
Quando o índice atrapalha
Toda escrita paga o preço de todos os índices da tabela. Em uma tabela que recebe milhares de INSERTs por minuto, cinco índices significam cinco estruturas para atualizar a cada linha.
Sinais de que um índice está sobrando:
- a consulta que ele deveria acelerar continua lenta;
- existe outro índice com as mesmas colunas iniciais;
- a coluna tem poucos valores distintos e aparece em quase todas as linhas;
- o ganho na leitura é menor que a perda na escrita.
Para testar o impacto sem apagar o índice:
ALTER INDEX IDX_TIMECARDS_PROF_DATA INACTIVE;
-- medir leitura e escrita
ALTER INDEX IDX_TIMECARDS_PROF_DATA ACTIVE;
Em carga massiva (importação de milhares de linhas), desativar índices não essenciais durante o processo e reativar depois costuma valer mais que qualquer ajuste de query.
Passo a passo para investigar uma query lenta
- Capture a consulta real e os parâmetros. Query genérica no papel esconde o problema.
- Rode
SET PLANONLY ONe veja se o índice esperado aparece. - Liste os índices existentes na tabela e confira a ordem das colunas.
- Compare a seletividade das colunas usadas no
WHERE. - Crie um índice candidato e meça com o mesmo volume de dados do cliente.
- Verifique o
INSERT/UPDATEda mesma tabela antes e depois. - Mantenha só o que ganhou. Índice sem uso é imposto sobre a escrita.
Checklist rápido
- Filtre por igualdade primeiro, faixa depois.
- Prefira colunas com muitos valores distintos.
- Leia o plano antes e depois da mudança.
- Recalcule estatísticas após grandes cargas.
- Evite função, aritmética e curinga no início da coluna filtrada.
- Meça também o custo de escrita, não só o tempo do
SELECT. - Remova índices redundantes.
Conclusão
Índice é uma ferramenta de precisão: resolve quando a coluna e a ordem certas encontram a consulta certa, e atrapalha quando vira reflexo automático. O caminho seguro é sempre o mesmo: medir com o plano, criar um candidato, comparar leitura e escrita e manter apenas o que comprovadamente ajuda.
Se você já sabe o que quer consultar mas não quer escrever o SQL na mão, o Sqlify gera a query Firebird a partir da descrição em português, com explicação de cada cláusula. Descreva algo como "apontamentos do profissional X no mês" e receba a consulta pronta para revisar e otimizar.
Leia também: