Cobertura de Consultas e Índices que Evitam Leitura da Tabela (Conceito de Covering)

Capítulo 8

Tempo estimado de leitura: 8 minutos

+ Exercício

O que é um “índice coberto” (covering index)

Um índice coberto é um índice que contém todas as colunas necessárias para a consulta: as colunas usadas em WHERE, as colunas retornadas no SELECT e (quando aplicável) as colunas usadas em ORDER BY ou GROUP BY. Quando isso acontece, o banco consegue responder a consulta lendo apenas o índice, sem precisar buscar o registro completo na tabela (o “row lookup”, “bookmark lookup” ou “table access”).

Na prática, “cobrir” significa: evitar leituras adicionais na estrutura principal da tabela (heap ou clustered), reduzindo I/O e latência.

Dois usos bem diferentes do índice

  • Usar índice para localizar: o índice ajuda a encontrar quais linhas servem, mas depois o banco precisa ir à tabela buscar colunas que não estão no índice.
  • Usar índice para responder: o índice já tem tudo o que a consulta precisa; o banco lê as entradas do índice e finaliza a consulta sem tocar na tabela.

Por que isso acelera: comparando custo em páginas lidas

Para visualizar o ganho, pense em custo como “quantas páginas (blocos) de disco/memória foram lidas”. Um índice B-tree costuma ter páginas menores e mais “densas” (mais entradas por página) do que páginas de dados da tabela. Além disso, quando a consulta não é coberta, cada linha encontrada no índice pode disparar uma leitura extra na tabela.

Modelo mental simples de custo

  • Consulta não coberta: páginas do índice (para achar as chaves) + páginas da tabela (para buscar colunas faltantes). Em muitos casos, vira: Index pages + N * (table page) (com N grande).
  • Consulta coberta: páginas do índice (para achar e retornar colunas). Em muitos casos: Index pages (e só).

Mesmo quando parte das páginas está em cache, reduzir leituras na tabela costuma reduzir CPU, latches/locks e pressão de memória.

Exemplo 1: Selecionar poucas colunas (evitando “lookup” na tabela)

Suponha uma tabela de pedidos:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

CREATE TABLE pedidos (  id BIGINT PRIMARY KEY,  cliente_id BIGINT NOT NULL,  status VARCHAR(20) NOT NULL,  criado_em TIMESTAMP NOT NULL,  total DECIMAL(10,2) NOT NULL,  observacao TEXT);

Consulta comum:

SELECT id, criado_em, total FROM pedidos WHERE cliente_id = 42 AND status = 'PAGO';

Sem índice coberto

Imagine que existe um índice apenas para filtrar:

CREATE INDEX idx_pedidos_cliente_status ON pedidos (cliente_id, status);

O banco usa o índice para localizar os id que batem com cliente_id e status, mas como precisa retornar criado_em e total, ele tende a fazer buscas na tabela para cada linha (ou para muitas delas).

Comparação de páginas lidas (exemplo ilustrativo):

CenárioLeituras típicasO que acontece
Índice para localizarpáginas do índice + muitas páginas da tabelaencontra chaves no índice e faz lookup na tabela para pegar colunas
Índice para responder (coberto)apenas páginas do índiceretorna colunas diretamente do índice

Com índice coberto

Crie um índice que inclua também as colunas retornadas:

-- Forma genérica: incluir colunas no índice (varia por SGBD)  -- Opção A (índice composto com colunas extras no final): CREATE INDEX idx_pedidos_coberto ON pedidos (cliente_id, status, criado_em, total, id);

Agora, para essa consulta, o banco pode ler as entradas do índice e já ter id, criado_em e total sem tocar na tabela.

Passo a passo prático

  • 1) Liste o que a consulta precisa: colunas do WHERE (cliente_id, status) + colunas do SELECT (id, criado_em, total).
  • 2) Garanta que o índice contenha todas: em SGBDs com INCLUDE, costuma-se colocar no “include” as colunas só retornadas; em outros, adiciona-se ao final do índice composto.
  • 3) Verifique o plano: procure por indicadores de “index-only scan”/“covering”/“using index” sem “lookup” na tabela.
  • 4) Meça páginas lidas: compare estatísticas de I/O antes/depois (leituras lógicas/físicas). O objetivo é reduzir leituras na tabela.

Exemplo 2: ORDER BY sem “filesort” e sem ler a tabela

Consulta:

SELECT id, criado_em, total FROM pedidos WHERE cliente_id = 42 ORDER BY criado_em DESC LIMIT 20;

Para ser eficiente, o banco precisa: (1) filtrar por cliente_id, (2) já entregar ordenado por criado_em, (3) retornar id e total sem ir à tabela.

Índice que ajuda a localizar, mas não cobre tudo

CREATE INDEX idx_pedidos_cliente_criado ON pedidos (cliente_id, criado_em DESC);

Esse índice pode evitar uma ordenação cara (porque já está na ordem), mas se total não estiver no índice, o banco ainda faz lookup na tabela para cada uma das 20 linhas (ou mais, se precisar examinar mais linhas antes de aplicar filtros adicionais).

Índice coberto para filtro + ordenação + retorno

CREATE INDEX idx_pedidos_cliente_criado_coberto ON pedidos (cliente_id, criado_em DESC, id, total);

Comparação de páginas lidas (exemplo ilustrativo com LIMIT):

  • Sem cobertura: lê páginas do índice para achar os 20 primeiros + até 20 lookups na tabela (potencialmente 20 páginas de dados, ou mais, dependendo de dispersão).
  • Com cobertura: lê páginas do índice e para ao atingir 20 entradas; não lê páginas da tabela.

Esse padrão é especialmente valioso com LIMIT, porque o banco pode parar cedo (early stop) lendo somente o índice.

Exemplo 3: Agregações simples com menos leituras

Agregações podem se beneficiar quando o índice contém as colunas do filtro e a coluna agregada, reduzindo acesso à tabela.

Consulta:

SELECT SUM(total) FROM pedidos WHERE cliente_id = 42 AND status = 'PAGO';

Índice não coberto

CREATE INDEX idx_pedidos_cliente_status ON pedidos (cliente_id, status);

O banco encontra as linhas pelo índice, mas precisa ler a tabela para obter total de cada linha. Se houver muitas linhas pagas, isso pode significar muitas páginas de dados lidas.

Índice coberto para a agregação

CREATE INDEX idx_pedidos_cliente_status_total ON pedidos (cliente_id, status, total);

Agora o banco pode somar total lendo apenas o índice (dependendo do otimizador e do SGBD, isso pode aparecer como varredura apenas no índice). Mesmo que ainda precise visitar algumas páginas, a tendência é reduzir leituras da tabela.

Comparação de custo em páginas (ilustrativo):

CenárioLeiturasObservação
Filtro no índice, total na tabelaíndice + muitas páginas de dadoscada linha agregada pode exigir acesso ao registro
Filtro e total no índiceprincipalmente páginas do índiceagregação pode ser feita com dados do índice

Como decidir o que “colocar no índice” para cobrir

Checklist rápido

  • Consultas que retornam poucas colunas e são muito frequentes são candidatas fortes.
  • Consultas com ORDER BY + LIMIT ganham muito quando o índice cobre e já está na ordem.
  • Relatórios simples (ex.: SUM, COUNT) com filtros seletivos podem reduzir leituras se a coluna agregada estiver no índice.

Passo a passo para desenhar um índice coberto

  • 1) Pegue a consulta real (com colunas exatas do SELECT, WHERE, ORDER BY).
  • 2) Defina o objetivo: eliminar lookup na tabela? eliminar sort? reduzir páginas lidas?
  • 3) Monte o índice: comece pelas colunas que determinam o acesso (filtros/ordenação) e adicione as colunas necessárias para retorno/agrupamento.
  • 4) Valide no plano: procure por operação “somente índice” e ausência de “lookup” na tabela.
  • 5) Compare I/O: antes/depois, compare leituras lógicas/físicas e tempo.

Ressalvas importantes: tamanho do índice e custo de manutenção

Índice coberto tende a ser maior

  • Adicionar colunas aumenta o tamanho do índice, o que pode significar mais páginas de índice para ler e manter.
  • Índices maiores reduzem a eficiência do cache: menos páginas “cabem” em memória, e isso pode afetar outras consultas.

Mais custo em INSERT/UPDATE/DELETE

  • INSERT: precisa inserir entradas maiores no índice.
  • UPDATE: se atualizar uma coluna presente no índice, o custo aumenta (pode exigir reescrita/movimentação de entradas).
  • DELETE: remove entradas do índice, gerando trabalho extra e possível fragmentação.

Evite “cobrir tudo” indiscriminadamente

  • Não tente colocar colunas grandes (ex.: TEXT, BLOB) no índice apenas para cobrir; isso costuma explodir o tamanho e piorar o custo geral.
  • Prefira cobrir consultas críticas e com retorno pequeno.
  • Se a consulta retorna muitas colunas (quase a linha inteira), o ganho de cobertura pode ser pequeno, e o índice pode ficar grande demais.

Como “enxergar” se a consulta está coberta

Embora a nomenclatura varie por SGBD, procure sinais no plano de execução de que a consulta foi resolvida sem acessar a tabela:

  • Operações do tipo index-only scan / covering index / using index (sem etapa de lookup).
  • Ausência de operações como table access by rowid, key lookup, bookmark lookup ou similares.
  • Queda clara em métricas de I/O: menos leituras de páginas da tabela e, idealmente, menos leituras totais.

Agora responda o exercício sobre o conteúdo:

Em qual cenário uma consulta tende a ser respondida sem fazer leituras adicionais na tabela (lookup), aproveitando um índice coberto?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Um índice coberto reúne todas as colunas exigidas pela consulta (filtro, retorno e eventualmente ordenação/agrupamento). Assim, o banco consegue finalizar a consulta lendo apenas o índice, evitando o lookup na tabela e reduzindo I/O.

Próximo capitúlo

Estatísticas e Estimativas: Por que o Otimizador Às Vezes Escolhe o Plano ‘Errado’

Arrow Right Icon
Capa do Ebook gratuito Índices e Performance de Banco de Dados para Iniciantes: Como Acelerar Consultas sem Mistério
57%

Índices e Performance de Banco de Dados para Iniciantes: Como Acelerar Consultas sem Mistério

Novo curso

14 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.