Seletividade e cardinalidade: o que são e por que importam
Cardinalidade é a quantidade de valores distintos em uma coluna dentro de uma tabela (ou dentro de um recorte, como “no último mês”).
Seletividade é o quão “filtrante” é um predicado (condição) sobre uma coluna. Uma forma prática de pensar é: seletividade ≈ linhas_retornadas / linhas_totais. Quanto menor esse número, mais seletivo é o filtro (melhor para reduzir o conjunto de linhas).
Em termos de escolha de colunas para indexar, colunas com alta cardinalidade (muitos valores distintos) costumam permitir filtros mais seletivos, especialmente em condições de igualdade (=) ou intervalos bem estreitos.
Exemplo numérico (tabela com 1.000.000 linhas)
| Coluna | Valores distintos (cardinalidade) | Exemplo de filtro | Linhas esperadas | Seletividade |
|---|---|---|---|---|
| status | 4 | status = 'PAGO' | 250.000 | 25% |
| cliente_id | 200.000 | cliente_id = 123 | ~5 | 0,0005% |
| pais | 20 | pais = 'BR' | 100.000 | 10% |
| data | 3.650 (10 anos) | data = '2026-01-01' | ~274 | 0,0274% |
Repare como status tem baixa cardinalidade e tende a retornar muitas linhas. Um índice em status pode trazer pouco benefício em muitas situações, porque o banco ainda precisará visitar uma grande quantidade de linhas.
Por que valores repetidos tendem a trazer menos benefício
Quando uma coluna tem poucos valores possíveis (ex.: status com 4 opções), um filtro típico (status = 'X') costuma selecionar uma fatia grande da tabela. Mesmo com índice, o banco pode acabar fazendo muitas leituras para buscar todas as linhas correspondentes.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Mini-simulação
Imagine que a consulta precisa retornar 250.000 linhas (25% da tabela). Mesmo que o índice encontre rapidamente “onde estão” essas linhas, o custo de buscar e processar 250.000 registros pode ser próximo (ou pior) do que varrer a tabela, dependendo de:
- tamanho da tabela e das páginas em disco
- quantas colunas a consulta precisa (se precisa buscar muitas colunas fora do índice)
- se os dados estão em cache
- se a consulta ainda precisa ordenar/agrupar
Distribuição de dados: uniforme vs concentrada
Cardinalidade sozinha não conta toda a história. A distribuição dos valores muda completamente a seletividade real.
Distribuição uniforme
Em uma distribuição aproximadamente uniforme, cada valor aparece com frequência parecida.
| status | Linhas | % |
|---|---|---|
| PAGO | 250.000 | 25% |
| PENDENTE | 250.000 | 25% |
| CANCELADO | 250.000 | 25% |
| ESTORNADO | 250.000 | 25% |
Nesse caso, qualquer filtro por status retorna muita coisa.
Distribuição concentrada (skew)
Agora considere que a maioria está em um único valor:
| status | Linhas | % |
|---|---|---|
| PAGO | 900.000 | 90% |
| PENDENTE | 80.000 | 8% |
| CANCELADO | 15.000 | 1,5% |
| ESTORNADO | 5.000 | 0,5% |
Aqui, um índice em status pode ser muito útil para consultas como status = 'ESTORNADO' (0,5%), mas pode ser pouco útil para status = 'PAGO' (90%).
Ou seja: a pergunta não é apenas “quantos valores distintos existem?”, mas também “qual valor eu filtro com mais frequência e qual a fatia que ele representa?”.
Valores nulos: como afetam seletividade e escolha de índice
NULL costuma se comportar como um “valor especial” em termos de distribuição: pode ser raro ou muito comum. Isso afeta a seletividade.
Exemplo: coluna data_pagamento
Suponha data_pagamento com muitos NULL (pedidos ainda não pagos):
| Condição | Linhas | % | Comentário |
|---|---|---|---|
data_pagamento IS NULL | 600.000 | 60% | Pouco seletivo |
data_pagamento IS NOT NULL | 400.000 | 40% | Ainda pouco seletivo |
data_pagamento = '2026-01-01' | 1.000 | 0,1% | Bem seletivo |
Se o padrão de consulta é “buscar pendências” com IS NULL, um índice nessa coluna pode não ajudar tanto (retorna muita linha). Se o padrão é “buscar pagamentos de um dia específico”, pode ajudar bastante.
Em alguns bancos, também é comum usar índices parciais/filtrados para lidar com NULL (ex.: indexar apenas linhas onde data_pagamento IS NULL), mas isso depende do SGBD e do caso de uso.
Como combinações de colunas aumentam seletividade
Quando uma coluna isolada não é seletiva o suficiente, combinar colunas pode reduzir drasticamente o número de linhas retornadas.
Exemplo: status vs (status + data)
Tabela com 1.000.000 linhas, distribuídas ao longo de 365 dias (1 ano). Suponha:
statustem 4 valores (uniforme): cada um ~25%datatem 365 valores: cada dia ~0,27%
Compare:
| Filtro | Estimativa de linhas | Seletividade |
|---|---|---|
status = 'PENDENTE' | 250.000 | 25% |
data = '2026-01-01' | ~2.740 | 0,274% |
status = 'PENDENTE' AND data = '2026-01-01' | ~685 | 0,0685% |
Mesmo que status sozinho seja fraco, a combinação com data pode ficar excelente para consultas que sempre filtram pelos dois.
Cuidado: correlação entre colunas
As contas acima assumem independência (distribuição “misturada”). Se houver correlação, a seletividade real muda. Exemplo: se “PENDENTE” ocorre quase todo em datas recentes, então status='PENDENTE' AND data='2026-01-01' pode retornar bem mais do que o estimado.
Na prática, você valida isso olhando contagens reais (amostras) e o plano de execução.
Passo a passo prático: como decidir quais colunas indexar
Passo 1 — Liste as consultas típicas (padrões de filtro)
Exemplos de consultas comuns em uma tabela pedidos:
Q1: listar pedidos de um cliente (recentes ou todos)Q2: listar pedidos por status (ex.: pendentes)Q3: relatórios por período (data entre X e Y)Q4: pedidos por país e períodoPasso 2 — Estime seletividade com números (mesmo que aproximados)
Use contagens aproximadas. Exemplo hipotético:
| Coluna | Cardinalidade | Distribuição | Observação |
|---|---|---|---|
| status | 4 | concentrada (90% PAGO) | bom para valores raros |
| data | 3.650 | quase uniforme por dia | bom para intervalos curtos |
| cliente_id | 200.000 | concentrada (clientes grandes) | ótimo para igualdade; varia por cliente |
| pais | 20 | concentrada (BR 60%) | sozinho costuma ser fraco |
Passo 3 — Priorize colunas que reduzem muito o conjunto de linhas nas consultas mais frequentes
- Filtros por
cliente_id = ?tendem a ser altamente seletivos. - Filtros por
datapodem ser muito seletivos se o intervalo for curto (ex.: 1 dia, 7 dias), e pouco seletivos se for longo (ex.: 2 anos). statusepaissozinhos muitas vezes retornam “fatias grandes” e podem não compensar.
Passo 4 — Considere combinações quando a consulta filtra por mais de uma coluna
Se a consulta típica filtra por cliente_id e também restringe por data, a combinação pode reduzir ainda mais leituras. Se filtra por pais e data para relatórios, a combinação pode ser mais interessante do que pais sozinho.
Passo 5 — Leve em conta o tamanho da tabela e o custo de manutenção
Em tabelas pequenas, a diferença pode ser imperceptível. Em tabelas grandes, qualquer filtro que evite ler uma grande fração da tabela tende a valer mais. Ao mesmo tempo, cada índice extra aumenta custo de escrita (inserções/atualizações) e ocupa espaço.
Tabelas hipotéticas para exercícios (status, data, cliente_id, país)
Cenário A — 10 milhões de pedidos (distribuição mista)
| Coluna | Resumo |
|---|---|
| status | 4 valores; PAGO 85%, PENDENTE 10%, CANCELADO 4%, ESTORNADO 1% |
| data | 2 anos (730 dias); últimos 30 dias concentram 35% dos pedidos |
| cliente_id | 500.000 clientes; top 1% gera 25% dos pedidos |
| pais | 30 países; BR 55%, US 15%, demais diluídos |
Consultas típicas
Q1: SELECT * FROM pedidos WHERE cliente_id = ? ORDER BY data DESC LIMIT 50;Q2: SELECT * FROM pedidos WHERE status = 'PENDENTE' AND data >= ? AND data < ?;Q3: SELECT count(*) FROM pedidos WHERE pais = 'BR' AND data >= ? AND data < ?;Q4: SELECT * FROM pedidos WHERE status = 'PAGO' AND pais = 'BR';Exercícios guiados (com perguntas e respostas)
Exercício 1 — Qual coluna indexar primeiro?
Pergunta: No Cenário A, qual coluna tende a ser a melhor candidata para acelerar Q1?
Resposta guiada: cliente_id, porque o filtro por igualdade geralmente reduz muito o conjunto de linhas. Além disso, Q1 ordena por data e limita 50 resultados; isso sugere que uma estratégia baseada em cliente_id é central para essa consulta.
Exercício 2 — status sozinho vale a pena?
Pergunta: Um índice apenas em status ajudaria Q2?
Resposta guiada: Depende do valor filtrado e da janela de datas. status='PENDENTE' é 10% (ainda 1 milhão de linhas em 10 milhões). Sozinho pode ser pesado. Mas Q2 também restringe por data; a seletividade real vem da combinação: “pendentes em um intervalo curto”. Então, pensar em indexar levando em conta ambos os filtros costuma fazer mais sentido do que status isolado.
Exercício 3 — país é fraco, mas pode ser útil?
Pergunta: Para Q3, indexar pais sozinho é uma boa?
Resposta guiada: Geralmente pais sozinho é pouco seletivo (BR 55%). Mas Q3 sempre combina pais com data. Se o intervalo de data for curto (ex.: 7 dias), data tende a ser o filtro mais seletivo; se for longo (ex.: 1 ano), a combinação com pais pode ajudar a reduzir leituras. A decisão depende do intervalo típico usado no relatório.
Exercício 4 — Quando um valor muito comum “estraga” o índice
Pergunta: Em Q4, status='PAGO' (85%) e pais='BR' (55%). Isso é seletivo?
Resposta guiada: Não muito. Mesmo combinando, a interseção pode continuar grande (por exemplo, se forem relativamente independentes: 0,85 × 0,55 ≈ 46,75% da tabela). Um índice para essa consulta pode não compensar, porque ainda retornaria quase metade das linhas. Aqui, a pergunta correta é: essa consulta realmente precisa retornar tantas linhas? Se sim, o gargalo pode não ser “achar” as linhas, mas processar/transferir o volume.
Checklist rápido: perguntas que você deve responder antes de indexar
- Qual é a seletividade real do filtro mais comum nessa coluna (não apenas a cardinalidade)?
- O valor filtrado é raro ou muito comum?
- A distribuição é uniforme ou concentrada (skew)?
- Há muitos NULL? As consultas filtram por
IS NULLou por valores específicos? - As consultas filtram por uma coluna ou por combinações?
- Qual é o tamanho da tabela e o volume de escrita (custo de manter índices)?
Prática orientada: estime seletividade com uma tabela de contagens
Use uma tabela de contagens (real ou hipotética) para decidir. Exemplo (Cenário A):
| Filtro | Linhas estimadas | Seletividade | Comentário |
|---|---|---|---|
cliente_id = 123 | 20 | 0,0002% | excelente |
status = 'ESTORNADO' | 100.000 | 1% | boa para esse valor |
status = 'PAGO' | 8.500.000 | 85% | ruim para esse valor |
pais = 'BR' | 5.500.000 | 55% | fraca sozinha |
data em 7 dias (últimos) | ~1.225.000 | 12,25% | depende do período |
status='PENDENTE' e 7 dias | ~122.500 | 1,225% | combinação melhora |
Pergunta final (para você responder): quais dessas condições aparecem com mais frequência no seu sistema? A coluna (ou combinação) que mais aparece nas consultas críticas e reduz mais linhas tende a ser a melhor candidata.