Como Índices Funcionam na Prática: Estruturas, Chaves e Ponteiros

Capítulo 2

Tempo estimado de leitura: 9 minutos

+ Exercício

O que um índice realmente armazena

Pense no índice como uma estrutura separada da tabela, otimizada para busca. Em termos práticos, ele armazena:

  • Chaves do índice: valores de uma ou mais colunas (ex.: cpf, email, data_pedido), organizados de forma que facilite localizar rapidamente um intervalo ou um valor específico.
  • Referências (ponteiros): um “endereço” para chegar ao dado real. Dependendo do banco/engine, essa referência pode ser o identificador da linha (RID), a chave primária, ou um ponteiro para a página/slot onde a linha está armazenada.

O ganho vem do fato de que, em vez de ler muitas páginas da tabela procurando linha a linha, o banco navega por poucas páginas do índice (que é menor e ordenado) e chega diretamente às referências das linhas candidatas.

Diagrama conceitual (em texto): índice como lista ordenada + ponteiros

ÍNDICE (chave ordenada)                 TABELA (linhas reais)  [10] -> (página 7, slot 3)  --------->  página 7: ... [slot 3] linha id=...  [15] -> (página 2, slot 9)  --------->  página 2: ... [slot 9] linha id=...  [21] -> (página 9, slot 1)  --------->  página 9: ... [slot 1] linha id=...

Quando você filtra por chave = 15, o banco tenta achar 15 no índice e, ao encontrar, segue o ponteiro para ler a linha na tabela.

Chave do índice: o que é e por que importa

A chave do índice é o(s) campo(s) usado(s) para ordenar e pesquisar dentro do índice. Ela pode ser:

  • Simples: uma coluna (ex.: cpf).
  • Composta: várias colunas em ordem (ex.: (estado, cidade, data_cadastro)).

Na prática, a ordem das colunas em um índice composto define quais predicados conseguem “entrar” no índice de forma eficiente. O banco tende a aproveitar melhor o prefixo da chave (as primeiras colunas).

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

Exemplo rápido: índice composto e prefixo

Índice: (estado, cidade, data_cadastro)

  • WHERE estado = 'SP' → consegue buscar um intervalo de estado no índice.
  • WHERE estado = 'SP' AND cidade = 'Campinas' → intervalo menor, mais seletivo.
  • WHERE cidade = 'Campinas' → geralmente não usa bem esse índice (falta o prefixo estado), a menos que o otimizador estime que ainda vale a pena por outros motivos.

Cardinalidade, duplicidade e seletividade (o “poder de filtrar”)

Cardinalidade é a quantidade de valores distintos em uma coluna (ou combinação de colunas). Ela influencia o quanto um índice ajuda.

  • Alta cardinalidade (muitos valores distintos, ex.: cpf, email): o índice tende a ser muito útil, porque cada busca retorna poucas linhas.
  • Baixa cardinalidade (poucos valores distintos, ex.: status com 3 valores): o índice pode ajudar em alguns casos, mas frequentemente retorna muitas linhas e o custo de “ir e voltar” para a tabela pode ficar alto.

Duplicidade é quando muitos registros compartilham o mesmo valor de chave. Índices aceitam duplicidade (a menos que sejam unique), mas isso afeta o custo: encontrar a chave é rápido, porém ler todas as referências associadas pode ser caro.

Microexercício 1: qual índice tende a filtrar melhor?

Uma tabela clientes tem 10 milhões de linhas.

  • Coluna sexo: valores {M, F, N} (3 distintos).
  • Coluna cpf: 10 milhões distintos.

Pergunta: em uma consulta que busca um único cliente, qual índice tende a reduzir mais leituras: idx_sexo ou idx_cpf? O que o banco precisaria ler em cada caso?

Páginas, níveis e custo de navegação no índice

Índices são armazenados em páginas (blocos) e, em estruturas comuns como B-Tree/B+Tree, organizados em níveis:

  • Raiz (root): ponto de entrada.
  • Níveis intermediários: direcionam a busca.
  • Folhas (leaf): onde ficam as chaves (ou intervalos) e as referências para as linhas/páginas.

O custo típico de uma busca por igualdade em um índice B-Tree é: ler algumas páginas do índice (uma por nível) até chegar à folha. Depois, para cada linha encontrada, pode haver leituras adicionais na tabela (a menos que o índice “cubra” a consulta, assunto que depende do banco e do tipo de índice).

Diagrama conceitual (em texto): navegação por níveis

          [ROOT]             /    \        [NÓ]      [NÓ]        /  \      /  \   [FOLHA][FOLHA][FOLHA][FOLHA]     |       |      |      |  chaves+refs ...

Se o índice tem 3 níveis, uma busca pode exigir algo como 3 leituras de páginas do índice (root + intermediário + folha), mais as leituras da tabela para buscar as linhas referenciadas.

Microexercício 2: estimando leituras

Imagine:

  • Índice com 3 níveis.
  • Predicado retorna 200 linhas.

Pergunta: quantas páginas o banco precisa ler no mínimo para localizar as referências (apenas no índice)? E quantas leituras adicionais podem ocorrer ao buscar as 200 linhas na tabela?

Clustering lógico vs físico (conceitual)

É comum confundir “ordem do índice” com “ordem física da tabela”. Conceitualmente:

  • Clustering lógico: o índice mantém as chaves em ordem e permite navegar por intervalos (ex.: datas de um mês). Isso é uma propriedade da estrutura do índice.
  • Clustering físico: refere-se a como as linhas estão de fato distribuídas nas páginas da tabela. Mesmo que o índice esteja ordenado, as linhas apontadas podem estar espalhadas em muitas páginas.

Quando as linhas correspondentes a um intervalo do índice estão fisicamente próximas, a leitura tende a ser mais sequencial e barata. Quando estão espalhadas, a consulta pode virar muitas leituras aleatórias.

Diagrama conceitual (em texto): mesmo índice, duas distribuições físicas

Caso A (mais agrupado fisicamente):  Índice (data) -> refs -> páginas 10,11,12 (poucas páginas)  Caso B (mais espalhado fisicamente):  Índice (data) -> refs -> páginas 3,18,44,105,... (muitas páginas)

Como o banco transforma um predicado em busca no índice

O otimizador tenta mapear o predicado para um padrão de acesso ao índice. Os padrões mais comuns:

  • Busca por igualdade: col = valor → navega até a chave e pega as referências.
  • Busca por intervalo: col BETWEEN a AND b, col >= a → navega até o início do intervalo e percorre folhas até o fim.
  • Prefixo textual: col LIKE 'abc%' → pode virar intervalo (abc até algo como abd, dependendo de collation/ordenação).
  • Funções na coluna: WHERE YEAR(data)=2025 → frequentemente impede o uso direto do índice na coluna, porque o índice está ordenado pelo valor original, não pelo resultado da função.

Exemplo 1 (numérico): igualdade

Tabela: contas(id, saldo). Índice: idx_saldo(saldo).

SELECT id FROM contas WHERE saldo = 1000;

Passo a passo do que tende a acontecer:

  • O banco identifica que existe índice em saldo.
  • Navega pelos níveis do índice até a folha onde saldo=1000 estaria.
  • Se houver duplicidade (muitas contas com saldo 1000), a folha terá várias referências.
  • Para cada referência, lê a linha (ou página) na tabela para retornar id (a menos que o índice já contenha id como parte da estrutura, dependendo do tipo de índice/engine).

Microexercício 3: duplicidade e custo

Se 50 mil contas têm saldo=1000, o índice ainda ajuda? Pergunta: o que fica barato (localizar a chave) e o que pode ficar caro (buscar muitas linhas)?

Exemplo 2 (texto): busca por prefixo

Tabela: usuarios(id, email). Índice: idx_email(email).

SELECT id FROM usuarios WHERE email LIKE 'ana%';

Como isso pode virar uma busca no índice:

  • O banco interpreta 'ana%' como um intervalo de chaves que começam com ana.
  • Navega até a primeira chave que atende (ex.: ana...).
  • Percorre as folhas em ordem enquanto as chaves ainda começam com ana.

Observação prática: LIKE '%ana' (com curinga no início) geralmente não consegue usar o índice como intervalo, porque não há um prefixo fixo para localizar o início.

Microexercício 4: qual predicado “entra” no índice?

  • A) email LIKE 'ana%'
  • B) email LIKE '%ana'

Pergunta: em qual caso o banco consegue navegar pelo índice começando de um ponto bem definido? O que muda no número de páginas lidas?

Exemplo 3 (datas): intervalo por período

Tabela: pedidos(id, data_pedido, total). Índice: idx_data(data_pedido).

SELECT id, total FROM pedidos WHERE data_pedido BETWEEN '2025-01-01' AND '2025-01-31';

Passo a passo típico:

  • O banco transforma BETWEEN em um intervalo ordenado.
  • Navega no índice até a primeira chave >= '2025-01-01'.
  • Varre as folhas em ordem até ultrapassar '2025-01-31'.
  • Coleta referências e busca as linhas correspondentes na tabela para retornar id e total.

Microexercício 5: intervalo pequeno vs grande

Compare:

  • A) Um dia: data_pedido = '2025-01-15'
  • B) Um ano: data_pedido BETWEEN '2025-01-01' AND '2025-12-31'

Pergunta: qual tende a percorrer mais páginas de folha do índice? Em qual caso a busca na tabela pode virar o maior custo?

Quando o índice aponta para a linha: ponteiros e “volta” à tabela

Após localizar as chaves no índice, o banco segue as referências para obter as colunas solicitadas. Isso cria um padrão comum:

  • Índice filtra: encontra rapidamente as chaves candidatas.
  • Tabela confirma e retorna: lê as linhas para pegar as demais colunas.

Se a consulta retorna poucas linhas, essa “volta” é barata. Se retorna muitas, o custo de buscar linha a linha pode superar o ganho do índice.

Diagrama conceitual (em texto): custo dividido

Custo total ≈ (leituras para navegar no índice) + (leituras para buscar linhas na tabela)  Navegação no índice: poucas páginas (níveis)  Busca na tabela: depende de quantas linhas e quão espalhadas estão

Checklist mental: “o que o banco precisa ler para responder?”

Ao olhar uma consulta com filtro, treine a estimar leituras respondendo:

  • Existe índice na(s) coluna(s) do predicado?
  • O predicado é igualdade, intervalo, prefixo, ou envolve função na coluna?
  • Quantas linhas o predicado tende a retornar (seletividade)?
  • Há muita duplicidade na chave?
  • Quantos níveis o índice provavelmente tem (tamanho do índice)?
  • As linhas estão fisicamente agrupadas ou espalhadas (clustering físico)?

Microexercício 6: interpretação guiada

Considere a tabela eventos(id, tipo, data_evento, usuario_id) com 100 milhões de linhas. Índices: idx_tipo(tipo) e idx_data(data_evento).

SELECT COUNT(*) FROM eventos WHERE tipo = 'LOGIN' AND data_evento >= '2026-01-01';

Perguntas:

  • Qual predicado parece mais seletivo: tipo (baixa cardinalidade) ou data_evento (muitos valores)?
  • Se o banco usar idx_tipo, o que ele precisará ler depois para aplicar o filtro de data?
  • Se usar idx_data, o que ele precisará ler depois para aplicar o filtro de tipo?

Use essas perguntas para treinar a intuição: índice não é “mágica”; ele reduz leituras quando consegue restringir cedo o conjunto de linhas, com navegação barata e poucas voltas à tabela.

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

Em um índice composto (estado, cidade, data_cadastro), qual consulta tende a aproveitar melhor o índice e por quê?

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

Você errou! Tente novamente.

Em índices compostos, o banco tende a usar melhor o prefixo da chave (primeiras colunas). Filtrar por estado e cidade permite localizar um intervalo menor no índice, reduzindo leituras.

Próximo capitúlo

Índices B-tree e Range Scans: Acelerar Filtros e Ordenações

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

Í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.