sqlpostgresqlindexperformance

Indices compostos no SQL: regra do prefixo a esquerda e ordem das colunas

Como um indice composto funciona pela regra do prefixo a esquerda, como ordenar colunas para filtro, ordenacao e intervalo, e quando ele vence dois indices simples.

3 min de leituraReferencesql · postgresql · index · performance · mysql

Um indice composto cobre varias colunas em uma unica estrutura. A ordem certa das colunas transforma filtro, ordenacao e ate a leitura das colunas necessarias em uma unica passagem pelo indice; a ordem errada o deixa quase inutil.

A regra do prefixo a esquerda

Uma B-tree ordena as linhas pelas colunas da esquerda para a direita, como palavras num dicionario. Por isso o indice so e aproveitado a partir da coluna mais a esquerda e sem lacunas.

CREATE INDEX idx_orders_user_created
  ON orders (user_id, created_at DESC);
  • WHERE user_id = 42 funciona, e o prefixo a esquerda.
  • WHERE user_id = 42 AND created_at > now() - interval '7 days' usa as duas colunas.
  • WHERE created_at > now() - interval '7 days' nao consegue usar este indice: a segunda coluna e inalcancavel sem a primeira.

Gotcha: a igualdade na primeira coluna "destrava" a ordenacao e os intervalos na segunda. Mas um intervalo na primeira coluna mata a busca pontual na segunda, porque depois de > ou < as linhas ja nao estao ordenadas pela coluna seguinte.

Ordem das colunas: filtro, depois ordenacao, depois intervalo

Regra pratica: primeiro as colunas de igualdade, depois a coluna de ordenacao e por ultimo a coluna de intervalo.

-- "pedidos recentes de um usuario, os novos primeiro"
SELECT id, amount, status
FROM orders
WHERE user_id = 42
  AND created_at >= '2026-01-01'
ORDER BY created_at DESC
LIMIT 20;

O indice (user_id, created_at DESC) e ideal: user_id restringe por igualdade e created_at DESC devolve as linhas ja ordenadas, entao nao e preciso um passo de ordenacao a parte.

  • A igualdade (=, IN) vem primeiro.
  • A coluna do ORDER BY vem logo depois das igualdades.
  • O intervalo (>, <, BETWEEN) vem por ultimo, porque quebra a ordem de tudo o que vem depois no indice.

Cobrir um ORDER BY e a direcao da ordenacao

Para o indice eliminar o passo de ordenacao, a direcao do ORDER BY precisa coincidir com o indice ou ser o seu inverso completo. O PostgreSQL consegue percorrer uma B-tree nos dois sentidos.

CREATE INDEX idx_emp_dept_salary
  ON employees (dept, salary DESC);

SELECT name, salary
FROM employees
WHERE dept = 'eng'
ORDER BY salary DESC
LIMIT 10;

ORDER BY salary ASC tambem funciona sem ordenar, e uma varredura reversa. Mas um ORDER BY dept ASC, salary ASC misto contra um indice (dept, salary DESC) forca uma reordenacao: as direcoes por coluna nao podem ser misturadas a vontade.

Colunas INCLUDE e index-only scan

Se o indice contem todas as colunas que a consulta precisa, o motor nao toca na tabela, e um index-only scan. As colunas pelas quais voce nao filtra ficam melhor adicionadas via INCLUDE: elas vivem so nas folhas e nao incham a navegacao pela arvore.

CREATE INDEX idx_orders_user_inc
  ON orders (user_id, created_at DESC)
  INCLUDE (amount, status);

SELECT created_at, amount, status
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC;
  • No PostgreSQL o index-only scan precisa de uma tabela com vacuum recente (o visibility map).
  • MySQL/InnoDB nao tem a palavra-chave INCLUDE, mas a chave primaria e anexada automaticamente a cada indice secundario, e um "covering index" se faz listando todas as colunas no proprio indice.
  • ClickHouse e diferente: seu indice primario e esparso e nao unico, e o ORDER BY da tabela define a ordem dos dados em disco.

Quando um composto vence dois indices simples

O planejador pode combinar dois indices separados em user_id e created_at via bitmap, mas isso sao duas leituras mais uma intersecao, mais lento que um unico indice que ja guarda os dados em ordem.

-- combinacao bitmap de dois indices: funciona, mas nao entrega ordem
CREATE INDEX idx_orders_user ON orders (user_id);
CREATE INDEX idx_orders_created ON orders (created_at);
  • Um indice composto vence quando as colunas sao sempre consultadas juntas em WHERE/ORDER BY.
  • Dois indices simples sao mais flexiveis quando as colunas tambem sao consultadas separadamente.
  • Nao exagere nos indices: cada um deixa INSERT/UPDATE mais lento e ocupa espaco. (a, b) ja serve para consultas em a, entao um indice a parte em a costuma ser redundante.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador