O que torna uma consulta “amiga de índices”
Mesmo com bons índices, uma consulta pode ser escrita de um jeito que impede o otimizador de usá-los bem. O objetivo aqui é escrever predicados e ordenações que permitam ao banco “entrar” no índice e navegar até o conjunto certo de linhas, em vez de varrer muita coisa e filtrar depois.
Na prática, boas práticas de escrita de queries buscam três efeitos no plano de execução:
- Trocar scan por seek: em vez de ler uma grande faixa (ou tudo) e filtrar, localizar diretamente o intervalo/valor no índice.
- Reduzir sort: evitar ordenações grandes quando a ordem já pode ser entregue pelo índice.
- Diminuir cardinalidade intermediária: filtrar cedo e de forma eficiente para que joins, agregações e ordenações processem menos linhas.
Predicados sargáveis (SARGable): o padrão mais importante
Um predicado é “sargável” quando pode ser aplicado diretamente sobre a coluna indexada, permitindo que o banco use o índice para localizar as linhas. O anti-padrão típico é “transformar a coluna” (com função, cálculo ou conversão) e só depois comparar.
Regra prática
- Bom: coluna “pura” do lado esquerdo, comparação com valor/parametro compatível do lado direito.
- Ruim: função/conversão/cálculo aplicado na coluna dentro do WHERE/JOIN.
Exemplos de reescrita conceitual (sem sintaxe específica)
| Intenção | Evite (não sargável) | Prefira (sargável) | Impacto esperado no plano |
|---|---|---|---|
| Filtrar por data (dia) | Aplicar função de “data sem hora” na coluna e comparar com uma data | Comparar a coluna com um intervalo: início do dia ≤ coluna < início do próximo dia | Maior chance de seek por intervalo; menos linhas lidas |
| Buscar por prefixo | Aplicar função para extrair prefixo e comparar | Usar comparação por intervalo/padrão que preserve o início do texto | seek em faixa; evita varredura completa |
| Filtrar por valor calculado | Comparar (coluna * fator) com um número | Reorganizar a expressão para comparar a coluna diretamente (quando matematicamente possível) | Permite uso do índice na coluna; reduz custo de CPU |
Evite funções e conversões do lado da coluna
Funções e conversões aplicadas na coluna geralmente impedem o uso eficiente do índice porque o banco não consegue “pular” para o ponto certo: ele teria que calcular o resultado para muitas linhas antes de saber se passam no filtro.
Padrões comuns que atrapalham
- Conversão de tipo na coluna: transformar a coluna para comparar com um parâmetro em outro tipo.
- Funções de data/texto: extrair partes (ano/mês/dia), normalizar texto, remover espaços, mudar caixa, etc.
- Operações aritméticas na coluna: multiplicar, dividir, somar, subtrair antes de comparar.
Passo a passo prático para corrigir
- Passo 1 — Identifique a coluna indexada no predicado: localize WHERE e JOIN que filtram por colunas com índice.
- Passo 2 — Verifique se há função/conversão na coluna: qualquer “embrulho” na coluna é suspeito.
- Passo 3 — Mova a transformação para o lado do valor: em vez de converter a coluna, converta o parâmetro/constante para o tipo da coluna.
- Passo 4 — Quando for data/intervalo, reescreva como faixa: use limites inferior/superior para representar “dia”, “mês”, “últimos N minutos”, etc.
- Passo 5 — Reavalie o plano: espere ver menos leituras e, frequentemente, troca de
scanporseek.
Compare tipos compatíveis (e evite conversões implícitas)
Mesmo sem escrever uma conversão explícita, o banco pode fazer uma conversão implícita para conseguir comparar tipos diferentes (por exemplo, comparar uma coluna numérica com um valor textual). Isso pode forçar conversão na coluna e derrubar a sargabilidade.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Boas práticas
- Parâmetros e literais no mesmo tipo da coluna: se a coluna é numérica, passe número; se é data/hora, passe data/hora; se é texto, passe texto no mesmo collation/forma esperada.
- Evite “números como texto” e “datas como texto”: além de conversões, pode gerar comparações erradas (ordem lexicográfica vs numérica/cronológica).
- Cuidado com tamanhos: comparar texto curto com texto longo pode gerar conversões e afetar estimativas.
Impacto esperado no plano
- Menos chance de conversão na coluna → maior chance de
seek. - Estimativas de cardinalidade mais estáveis → menos risco de escolhas ruins (por exemplo, join pesado por subestimar linhas).
Selecione apenas as colunas necessárias (quando possível)
Trazer colunas demais aumenta o volume de dados trafegado e pode forçar o banco a buscar informações adicionais. Mesmo quando o filtro usa índice, um SELECT “gordo” pode aumentar leituras e memória.
Práticas objetivas
- Evite SELECT “tudo”: liste apenas colunas usadas pela tela/relatório/processo.
- Separe consulta de listagem da consulta de detalhe: primeiro traga IDs e poucos campos; depois busque detalhes apenas para os itens necessários.
- Evite colunas grandes sem necessidade: textos longos e blobs ampliam I/O e memória.
Impacto esperado no plano
- Menos I/O e menor custo de operadores acima (sort, hash, agregação).
- Menor cardinalidade “em bytes” (mesmo com mesma quantidade de linhas), reduzindo spills para disco em operações de memória.
Paginação com ordenação consistente (evite “pular páginas” caro)
Paginação ingênua costuma ficar mais lenta conforme o número da página cresce, porque o banco precisa localizar e descartar muitas linhas antes de retornar o próximo bloco. Além disso, ordenação instável (sem critério único) pode causar itens repetidos ou faltando entre páginas.
Padrão recomendado
- Ordene por uma chave estável e única: por exemplo, (data_criacao, id) em vez de só data_criacao.
- Prefira paginação por “marcador” (seek method): em vez de “pular N linhas”, use “traga os próximos X itens após (último_valor_de_ordem)”.
Exemplo conceitual de reescrita
| Cenário | Evite | Prefira | Impacto esperado no plano |
|---|---|---|---|
| Listar pedidos por data | Paginar com “pular N e pegar X” ordenando por data | Paginar com “pegar X onde (data,id) > (última_data,último_id)” mantendo a mesma ordenação | Evita trabalho crescente por página; favorece seek e reduz sort |
Passo a passo prático para implementar paginação estável
- Passo 1: escolha uma ordenação determinística (inclua um desempate único).
- Passo 2: retorne junto o “marcador” (os campos de ordenação) para o cliente.
- Passo 3: na próxima página, filtre por “maior que o marcador” (ou “menor que”, se ordem descendente).
- Passo 4: mantenha a mesma ordenação e o mesmo filtro base em todas as páginas.
Filtros alinhados com índices compostos (sem repetir teoria)
Quando existe um índice com múltiplas colunas, o formato do filtro e da ordenação pode determinar se o banco consegue aproveitar a estrutura do índice ou se vai cair em leituras maiores e ordenações extras.
Padrões de escrita que ajudam
- Use condições que respeitem a ordem do índice: coloque no WHERE as colunas iniciais do índice sempre que fizer sentido para o caso de uso.
- Evite “pular” a primeira coluna: filtrar apenas por uma coluna posterior tende a reduzir o aproveitamento do índice.
- Combine filtro + ordenação compatíveis: se você filtra por (A) e ordena por (A,B), frequentemente evita sort; se ordena por colunas fora do índice, pode surgir sort.
- Evite OR amplo em colunas diferentes: OR pode impedir um caminho único no índice; quando aplicável, reescreva como união de consultas mais específicas (mantendo o mesmo resultado).
Exemplo conceitual (alinhamento filtro/ordem)
Intenção: buscar eventos de um cliente em um período e listar por data.
- Evite: filtrar só pelo período e depois ordenar por cliente e data (ou ordenar por uma coluna não alinhada).
- Prefira: filtrar por cliente e período e ordenar por data (com desempate), na mesma direção da ordenação esperada.
Impacto esperado: menos linhas candidatas (cardinalidade intermediária menor) e menor chance de sort, porque a ordem pode vir do índice.
Reescritas que reduzem cardinalidade intermediária
Mesmo quando o índice é usado, o plano pode ficar caro se a consulta produz um conjunto intermediário grande antes de aplicar filtros finais. A ideia é “empurrar” filtros seletivos para o mais cedo possível e evitar operações que explodem linhas.
Padrões úteis
- Filtre antes de juntar: aplique condições seletivas na tabela que mais reduz linhas antes do join (quando o resultado lógico permitir).
- Evite expressões que impedem filtro cedo: por exemplo, calcular um campo derivado e filtrar por ele depois; prefira filtrar pelos componentes originais.
- Cuidado com LIKE com curinga no início: tende a impedir navegação eficiente; quando inevitável, trate como busca que pode exigir varredura.
Impacto esperado no plano
- Menos linhas entrando em joins e agregações → menos CPU/memória.
- Menor probabilidade de sort/hash grandes e de operações que “derramam” para disco.
Checklist: revise a query antes de criar novos índices
- Predicados sargáveis: há função, conversão ou cálculo aplicado na coluna do WHERE/JOIN? Se sim, dá para reescrever para comparar a coluna diretamente?
- Tipos compatíveis: parâmetros/literais têm o mesmo tipo da coluna? Existe risco de conversão implícita na coluna?
- Filtros por intervalo: filtros por dia/mês/horário foram escritos como faixa (limite inferior/superior) em vez de extrair partes da data?
- OR e condições complexas: OR amplo está impedindo um caminho eficiente? Dá para reescrever em partes equivalentes (mantendo o resultado)?
- Ordenação: o ORDER BY é determinístico (inclui desempate único)? Ele está alinhado com os filtros e com a ordem do índice existente?
- Paginação: está usando “marcador” (seek) em vez de pular grandes quantidades? A ordenação é consistente entre páginas?
- Colunas retornadas: o SELECT traz apenas o necessário? Há colunas grandes sendo retornadas sem uso?
- Cardinalidade intermediária: dá para aplicar filtros seletivos mais cedo (antes de joins/agregações) sem mudar o resultado?
- Plano esperado: a reescrita tende a trocar
scanporseek, reduzirsorte diminuir linhas processadas nos operadores caros?