MongoDB: Fundamentos e princípios da indexação
Última atualização: 2026-08-26
Os índices são fundamentais para o desempenho do banco de dados — dominar os princípios e a otimização dos índices pode aumentar a velocidade das consultas em 1.000 vezes.
1. O que você vai aprender
- Princípios de indexação (estrutura de dados B-Tree)
- Sintaxe de createIndex()
- Índices de coluna única e índices compostos
- Interpretando o plano de execução do
explain() - Cobertura do índice (consulta coberta)
- Custos e compromissos do índice
2. Princípios de indexação (árvore B)
Explicação do conceito: Um índice é uma estrutura de dados auxiliar, semelhante ao índice de um livro, que permite que um banco de dados localize rapidamente um documento sem precisar vasculhar toda a coleção. O MongoDB utiliza uma estrutura de índice baseada em árvore B (o mecanismo WiredTiger usa uma variante da árvore B+) para estabelecer um mapeamento ordenado entre os valores dos campos e as localizações dos documentos, reduzindo a complexidade temporal das consultas de O(N) para O(log N).
Como funciona: Os índices B+Tree do MongoDB armazenam os valores dos campos nos nós internos para roteamento e armazenam os valores reais das chaves e os ponteiros dos documentos nos nós-folha. Os nós-folha são conectados por meio de uma lista duplamente encadeada, que suporta naturalmente consultas por intervalo e ordenação. Quando uma consulta corresponde a um índice, o mecanismo compara os valores das chaves camada por camada, começando pelo nó raiz, localizando, por fim, a posição física do documento alvo em um nó folha, evitando assim uma varredura completa da coleção (COLLSCAN).
Casos de uso:
- Campos frequentemente utilizados como critérios de consulta (como
skueuserId) - Campo de classificação (por exemplo,
createdAt: -1) - Campos que exigem uma restrição de exclusividade (por exemplo,
email) - Não recomendado para: campos com baixa seletividade (por exemplo,
isActive, que aceita apenas valores verdadeiros ou falsos) e campos que são atualizados com muita frequência, mas consultados muito raramente
graph TB
A[Root Node<br/>50-100] --> B[Internal Node 1<br/>20-50]
A --> C[Internal Node 2<br/>20-50]
B --> D[Leaf Node 1<br/>Link to the document]
B --> E[Leaf Node 2]
C --> F[Leaf Node 3]
C --> G[Leaf Node 4]
D <--> E <--> F <--> G
style A fill:#cce5ff
style D fill:#d4edda
style E fill:#d4edda
style F fill:#d4edda
style G fill:#d4edda
Processo de consulta na árvore B+: Para uma consulta de igualdade, as comparações são feitas camada por camada, começando pelo nó raiz → até se chegar a um nó folha → e, então, o ponteiro do documento é retornado; para uma consulta de intervalo, após localizar o nó folha inicial, a lista encadeada é percorrida sequencialmente → e todos os documentos que atendem aos critérios são coletados.
sequenceDiagram
participant App as Application Inquiry
participant WT as WiredTigerEngine
participant IX as B+TreeIndex
participant DOC as Collection of Documents
App->>WT: find({sku: 'SKU-005'})
WT->>IX: Root Node Comparison 50<005<100 → Zuo Zishu
IX-->>WT: Internal Node: 20<005<50 → Left lobe
WT->>IX: Leaf Node Search SKU-005
IX-->>WT: Found → doc_pointer=0x7F3A
WT->>DOC: Read 0x7F3A Location Documentation
DOC-->>App: Return matching documents
Note over WT,IX: Time Complexity O(log N)<br/>No need to scan all the documents
| Operação | Varredura completa da tabela (COLLSCAN) | Índice B+Tree (IXSCAN) | Diferença de desempenho |
|---|---|---|---|
| Consulta de valores iguais | O(N) | O(log N) | 100.000 linhas: 100 mil vs 17 |
| Consulta de intervalo | O(N) | O(log N + K) | 100.000 linhas: 100 mil vs 17 mil+ |
| Classificação | O(N log N) | O(log N + K) | O índice já está naturalmente classificado, portanto não é necessária nenhuma classificação |
| Inserir/Atualizar | O(1) | O(log N) | Sobrecarga adicional para manutenção do índice |
(1) Detalhes sobre o armazenamento de índices do WiredTiger
| Dimensão | Descrição |
|---|---|
| Formato do índice | Árvore B+, pares chave-valor armazenados em ordem classificada |
| Nó folha | Contém a chave de índice + RecordID (ponteiro de localização do documento) |
| Nó interno | Contém apenas a chave da rota e um ponteiro para um nó filho |
| Conexão em lista encadeada | Os nós-folha formam uma lista duplamente encadeada, permitindo a varredura sequencial |
| Compressão | A compressão por prefixo reduz o espaço de armazenamento |
3. Sintaxe do createIndex
Explicação do conceito: createIndex() é o comando principal para criar índices no MongoDB; ele instrui o mecanismo a construir uma estrutura de índice B+Tree para o campo especificado. Uma vez criado o índice, o otimizador de consultas determina automaticamente se deve utilizá-lo — os desenvolvedores não precisam modificar a instrução de consulta.
Como funciona: Ao criar um índice, o MongoDB analisa todos os documentos da coleção, extrai os valores dos campos indexados, os classifica e constrói uma estrutura B+Tree que é gravada no disco. Durante esse processo, é aplicado um bloqueio de gravação à coleção (os índices podem ser criados em segundo plano background: true), e a criação de um índice em uma coleção grande pode levar de alguns minutos a várias horas.
Regras gramaticais:
| Parâmetro | Tipo | Descrição |
|---|---|---|
keys |
objeto | Campos de índice e ordem: 1 ascendente, -1 descendente |
unique |
booleano | Indica se é um índice exclusivo; o valor padrão é false |
background |
booleano | Indica se a compilação deve ser feita em segundo plano (sem bloquear operações de leitura/gravação); o valor padrão é false |
name |
string | Nome do índice personalizado; o padrão é field_1 |
partialFilterExpression |
objeto | Critérios de indexação parcial (somente os documentos que atendem aos critérios são indexados) |
sparse |
booleano | Índice esparso; ignorar campos nulos |
expireAfterSeconds |
número | Índice TTL, expira automaticamente e é excluído (segundos) |
v |
número | Versão do índice; o padrão é v=2 |
// === Create a single-field index ===
db.products.createIndex({ sku: 1 }); // Ascending
db.products.createIndex({ createdAt: -1 }); // Descending
// === Create a composite index ===
db.products.createIndex({ category: 1, price: -1 });
// === Create a unique index ===
db.products.createIndex({ sku: 1 }, { unique: true });
// === Create in the Backend(Non-blocking)===
db.products.createIndex({ tags: 1 }, { background: true });
// === Custom Index Name ===
db.products.createIndex({ title: 1 }, { name: 'idx_title' });
Análise dos pontos-chave:
- Os valores 1 e -1 afetam a ordem de classificação dos índices; eles não têm impacto prático nos índices de campo único, mas são cruciais para a otimização da classificação em índices compostos.
background: trueNo MongoDB 4.2 e versões posteriores, isso é executado por padrão em segundo plano; os parâmetros são mantidos, mas não precisam mais ser especificados explicitamente.- Cada coleção possui um índice único padrão chamado
_id(que não pode ser excluído); não há necessidade de criar outro.
▶ Exemplo 1: createIndex e monitoramento da criação de índices
// ShopHub E-commerce:Create a key index for the product collection,Monitoring Build Progress
db.products.createIndex({ category: 1, price: -1, rating: -1 }, { name: 'idx_category_price_rating', background: true });
// View Index Build Progress
db.currentOp({
$or: [
{ op: 'command', 'command.createIndexes': { $exists: true } },
{ op: 'none', ns: /shopdb\.products/ }
]
});
// View All Indexes
db.products.getIndexes();
// [
// { v: 2, key: { _id: 1 }, name: '_id_' },
// { v: 2, key: { category: 1, price: -1, rating: -1 }, name: 'idx_category_price_rating' }
// ]
4. Plano de execução do explain()
Explicação do conceito: explain() é uma ferramenta de análise de consultas para o MongoDB que retorna o plano de execução selecionado pelo otimizador de consultas, incluindo métricas-chave, como se um índice foi utilizado, quantos documentos foram examinados e quanto tempo a operação levou. Ela funciona como um “raio-X” para a otimização de índices — execute explain() primeiro e, em seguida, otimize.
Como funciona: O otimizador de consultas do MongoDB gera vários planos candidatos para cada consulta, os executa, compara seu desempenho e armazena em cache o plano ideal. explain() Apresenta três níveis:
queryPlanner: Plano selecionado pelo otimizador (não executado)executionStats: Estatísticas reais de execução (requer o parâmetro'executionStats')allPlansExecution: Estatísticas de execução para todos os planos candidatos
graph LR
A[Query Request] --> B[Query Optimizer]
B --> C[Generate Candidate Plans]
C --> D[Plan A: IXSCAN]
C --> E[Plan B: COLLSCAN]
D --> F[Execution Comparison]
E --> F
F --> G[Selecting the Optimal Plan]
G --> H[Cache + Execute]
style G fill:#d4edda
style E fill:#f8d7da
Casos de uso:
- Se uma consulta estiver lenta, use
explain()para verificar se ela utilizou um índice - Antes de implementar um novo índice, verifique se as consultas retornam resultados
- Comparação das diferenças de desempenho entre diferentes esquemas de indexação
- Verificou-se que
COLLSCAN(varredura completa da tabela) precisa ser otimizado
// === View the query execution plan ===
db.products.find({ category: 'Electronics' }).explain('executionStats');
// === Key Metrics ===
{
queryPlanner: {
winningPlan: {
stage: 'IXSCAN', // Index Scan(✅)
// stage: 'COLLSCAN', // Exhaustive Scan(❌)
inputStage: {
stage: 'IXSCAN',
indexName: 'category_1'
}
}
},
executionStats: {
totalDocsExamined: 250, // Number of scanned documents
totalKeysExamined: 250, // Number of Scan Index Keys
nReturned: 250, // Number of documents returned
executionTimeMillis: 5, // Execution Time(milliseconds)
totalQueryPlanExecutionTime: 7
}
}
(1) Interpretação dos principais indicadores
| Indicador | Valor-alvo | Problema | Descrição |
|---|---|---|---|
stage |
IXSCAN | COLLSCAN (varredura completa da tabela) | O COLLSCAN requer otimização de índice |
totalDocsExamined |
≈ nReturned | Muito maior que nReturned | Um valor mais de 10 vezes maior indica baixa precisão do índice |
totalKeysExamined |
≈ nReturned | Muito maior que nReturned | O intervalo da varredura do índice é muito grande |
executionTimeMillis |
< 50 ms | > 100 ms | Considere a cobertura do índice ou índices compostos |
indexName |
Nome do índice de destino | id ou nenhum | Confirme se o índice esperado foi encontrado |
Comparação dos três modos de explicação:
| Modo | Saída | Cenários aplicáveis |
|---|---|---|
'queryPlanner' |
Apenas planejamento, sem execução | Verifique rapidamente se o índice está sendo usado |
'executionStats' |
Estatísticas de planejamento e execução | Análise de desempenho (mais comum) |
'allPlansExecution' |
Estatísticas de todos os planos candidatos | Análise da seleção do otimizador |
▶ Exemplo 2: Usando explain() para diagnosticar consultas lentas
// TechCorp: The system has detected that order lookups are becoming slower. Use explain to diagnose
// Before the Index:COLLSCAN
db.orders.find({ status: 'paid', total: { $gte: 100 } }).explain('executionStats');
// stage: 'COLLSCAN', totalDocsExamined: 100000, executionTimeMillis: 450
// Create a composite index
db.orders.createIndex({ status: 1, total: -1 });
// After indexing:IXSCAN
db.orders.find({ status: 'paid', total: { $gte: 100 } }).explain('executionStats');
// stage: 'IXSCAN', indexName: 'status_1_total_-1'
// totalDocsExamined: 5000, nReturned: 5000, executionTimeMillis: 8
// Efficiency Assessment: examined/returned = 1.0 (Optimal Value), Performance improved 56x
5. Índices compostos e o prefixo mais à esquerda
Explicação do conceito: Um índice composto é um único índice criado a partir de vários campos (por exemplo, { category: 1, price: -1, rating: 1 }). Ele é mais eficiente do que vários índices de campo único, pois uma única consulta ao índice pode atender a várias condições de consulta simultaneamente. O princípio do “prefixo mais à esquerda” é a regra fundamental dos índices compostos — o índice só é eficaz quando os campos são utilizados consecutivamente, começando pelo campo mais à esquerda.
Como funciona: Um índice composto constrói uma árvore B+ com base na ordem dos campos. Primeiro, ele classifica pelo primeiro campo; se os valores do primeiro campo forem iguais, ele classifica pelo segundo campo e assim por diante. Durante uma consulta, a correspondência deve começar pelo campo mais à esquerda; pular quaisquer campos prefixais tornará os campos subsequentes no índice ineficazes — assim como você precisa primeiro determinar a primeira letra ao procurar uma palavra em um dicionário.
graph TB
subgraph "Composite Index {category, price, rating}"
A[Electronics<br/>$100<br/>★5] --> B[Electronics<br/>$200<br/>★4]
B --> C[Electronics<br/>$300<br/>★3]
C --> D[Books<br/>$10<br/>★5]
D --> E[Books<br/>$20<br/>★4]
end
subgraph "Query Hit Analysis"
F["✅ {category}"] --> G["✅ {category, price}"]
G --> H["✅ {category, price, rating}"]
I["❌ {price}"] --> J["Skip category"]
K["❌ {rating}"] --> L["Skip category, price"]
M["❌ {category, rating}"] --> N["Skip price"]
end
style F fill:#d4edda
style G fill:#d4edda
style H fill:#d4edda
style I fill:#f8d7da
style K fill:#f8d7da
style M fill:#f8d7da
Casos de uso:
- Consultas com múltiplos critérios (por exemplo,
category + price) - Combinações de filtro + classificação (por exemplo,
status = 'paid'+sort by createdAt) - Não recomendado: Consultar cada condição separadamente (nesse caso, vários índices de campo único são mais flexíveis)
| Padrão de consulta | Corresponde a {categoria, preço, avaliação} | Motivo |
|---|---|---|
{category: 'A'} |
✅ Todas as correspondências | Use o prefixo “categoria” |
{category: 'A', price: {$gte: 100}} |
✅ Todos os resultados | Use o prefixo “categoria + preço” |
{category: 'A', price: {$gte: 100}, rating: 5} |
✅ Todas as correspondências | Correspondência exata em três campos |
{price: {$gte: 100}} |
❌ Nenhuma correspondência | Ignorar a categoria mais à esquerda |
{rating: 5} |
❌ Nenhuma correspondência | Ignorar categoria, preço |
{category: 'A', rating: 5} |
⚠️ Apenas “categoria” e “preço” estão com problemas; “avaliação” não está disponível |
// === Composite Index:{ category: 1, price: -1, rating: 1 } ===
db.products.createIndex({ category: 1, price: -1, rating: 1 });
// ✅ Queries Using Indexes:
db.products.find({ category: 'Electronics' }); // Uses category
db.products.find({ category: 'Electronics', price: { $gte: 100 } }); // Uses category + price
db.products.find({ category: 'Electronics', price: { $gte: 100 }, rating: 5 }); // Use all 3 Field
// ⚠️ Queries That Do Not Use Indexes:
db.products.find({ price: { $gte: 100 } }); // Skip category
db.products.find({ rating: 5 }); // Skip category, price
db.products.find({ category: 'Electronics', rating: 5 }); // Skip price
Princípio do prefixo mais à esquerda: Os índices compostos só são eficazes quando usados consecutivamente, começando pelo campo mais à esquerda. Pular campos intermediários rompe a cadeia do índice — os campos subsequentes não podem utilizar o índice.
Regra ESR (Igualdade → Ordenação → Intervalo): A regra de ouro para a ordem dos campos em um índice composto — os campos de filtro de igualdade vêm primeiro, os campos de ordenação no meio e os campos de consulta de intervalo por último. Esse assunto será abordado em profundidade em uma lição posterior (Lição 19).
6. Cobertura do índice
Explicação do conceito: Uma consulta coberta é aquela em que todos os campos necessários para a consulta estão incluídos no índice, permitindo que o mecanismo retorne resultados diretamente do índice, sem a necessidade de buscar o documento original. Essa é a forma definitiva de otimização do índice — a consulta não requer nenhum acesso aos dados do documento.
Como funciona: O processo padrão de consulta é “varredura do índice → recuperação do ponteiro do documento → acesso ao documento na tabela → extração dos campos → retorno”; o processo de consulta baseado em índice é “varredura do índice → extração dos campos diretamente do índice → retorno”. Ao eliminar a necessidade de acessar a tabela, as operações de E/S são reduzidas pela metade e o desempenho melhora em mais de 50%.
graph LR
subgraph "General Inquiry"
A1[Index Scan] --> A2[Get doc pointer]
A2 --> A3[Return to the table and read the document]
A3 --> A4[Extract Fields]
A4 --> A5[Return Results]
end
subgraph "Index-Covered Queries"
B1[Index Scan] --> B2[Directly Extract Index Fields]
B2 --> B3[Return Results]
end
style A3 fill:#f8d7da
style B2 fill:#d4edda
Casos de uso:
- As consultas de alta frequência exigem apenas alguns campos (por exemplo, a página de lista exibe apenas
sku, title, price) - Grande acervo de documentos; alto custo das consultas
- Condição principal: A projeção deve excluir
_id(_id: 0), e todos os campos da projeção devem constar no índice
| Dimensão | Consulta normal | Cobertura do índice |
|---|---|---|
| Fluxo de execução | Varredura de índice → Pesquisar documento na tabela | Varredura de índice → Retornar diretamente |
| Número de operações de E/S | 2 (índice + documento) | 1 (apenas índice) |
| Desempenho | Teste de desempenho | 50% mais rápido |
| explicar indicador | FETCH estágio |
PROJECTION_COVERED |
| Restrições | Nenhuma | A projeção deve excluir _id |
// === General Inquiry:I need to go back to the table to check the documentation. ===
db.products.find(
{ category: 'Electronics' },
{ sku: 1, title: 1, price: 1 }
);
// === Index-Covered Queries:Returned directly from the index ===
db.products.createIndex({ category: 1, sku: 1, title: 1, price: 1 });
db.products.find(
{ category: 'Electronics' },
{ sku: 1, title: 1, price: 1, _id: 0 } // _id: 0 Must be excluded
);
// explain Displayed in the middle stage: 'PROJECTION_COVERED' ✅
Análise dos pontos-chave:
_idé sempre retornado por padrão e não está incluído no índice regular; é necessário usar_id: 0para excluí-lo, a fim de substituí-lo.- Quanto mais campos de índice houver, mais consultas eles abrangem, mas maior fica o tamanho do índice — é preciso encontrar um equilíbrio.
- A cobertura por índice é mais eficaz para documentos grandes (a redução na quantidade de E/S é proporcional ao tamanho do documento).
7. Gerenciamento de índices
Explicação do conceito: O gerenciamento de índices inclui a criação, visualização, exclusão e reconstrução de índices. Em um ambiente de produção, os índices não são algo que se possa simplesmente “configurar e esquecer”; é necessário monitorar continuamente seu uso, excluir índices não utilizados e reconstruir índices fragmentados.
Ciclo de vida do índice:
graph LR
A[Analyzing Query Patterns] --> B[Design Index]
B --> C[Create an Index<br/>background:true]
C --> D[Validate Hit<br/>explain()]
D --> E[Monitoring Utilization<br/>$indexStats]
E --> F{Low utilization rate?}
F -->|Yes| G[Delete Index]
F -->|No| H[Continue monitoring]
G --> A
H --> E
style G fill:#f8d7da
style D fill:#d4edda
| Operações Administrativas | Comando | Descrição |
|---|---|---|
| Ver Índice | db.col.getIndexes() |
Listar todos os índices e definições de chaves |
| Excluir índice | db.col.dropIndex(name) |
Excluir por nome ou definição de chave |
| Excluir tudo | db.col.dropIndexes() |
Excluir todos os índices (manter _id) |
| Recriar índice | db.col.reIndex() |
Recriar todos os índices (desfragmentar) |
| Tamanho do índice | db.col.totalIndexSize() |
Visualizar o uso de espaço do índice (bytes) |
| Uso do índice | db.col.aggregate([{$indexStats:{}}]) |
Visualizar o número de vezes que cada índice foi utilizado |
// === View all indexes in the collection ===
db.products.getIndexes();
// === Delete Index ===
db.products.dropIndex('sku_1');
db.products.dropIndex({ sku: 1 });
// === Delete all indexes(Retain _id)===
db.products.dropIndexes();
// === Rebuild Index ===
db.products.reIndex();
// === View Index Size ===
db.products.totalIndexSize();
Análise dos pontos-chave:
reIndex()Bloqueia a coleção; em um ambiente de produção, recomenda-se executar isso durante uma janela de manutenção.dropIndex()Recomendamos usar primeiro o$indexStatspara confirmar que o índice realmente não está em uso.- Recomenda-se que cada índice definido não contenha mais do que 10 entradas; um número excessivo de entradas afetará o desempenho de gravação.
8. Custo do índice
Explicação do conceito: Os índices não são gratuitos — cada índice ocupa espaço em disco, aumenta a sobrecarga de gravação e consome memória. Compreender o custo dos índices é fundamental para fazer as escolhas certas. “Quanto mais índices, melhor” é o equívoco mais comum.
Como funciona: Sempre que um documento é inserido, atualizado ou excluído, o MongoDB precisa atualizar de forma síncrona as estruturas B+Tree de todos os índices relevantes. Se uma coleção tiver N índices, as operações de gravação exigem a manutenção de N B+Trees. Quanto mais índices houver, mais lentas se tornam as gravações e maior é o uso de memória (já que o cache do WiredTiger precisa carregar as páginas dos índices).
graph LR
A[Create an index] --> B[Faster reads]
A --> C[Slower writes]
A --> D[Takes up space]
B --> B1[Search +1000x]
C --> C1[Insert/Update +50% Expenses]
D --> D1[Per Index 1-10 MB]
subgraph "Weighing Decisions"
E[High query frequency?] -->|Yes| F[✅ Create an index]
E -->|No| G[❌ Do not build]
H[High write frequency?] -->|Yes| I[⚠️ Caution]
H -->|No| F
end
Quantificando o custo da indexação:
| Dimensão de custo | Impacto | Quantificação |
|---|---|---|
| Espaço em disco | Cada índice ocupa aproximadamente 5–20% do volume de dados | 10 GB de dados × 8 índices ≈ 4–16 GB de espaço adicional |
| Latência de gravação | Cada índice adicional aumenta o tempo de gravação em cerca de 5–10% | 8 índices → As gravações ficam 40–80% mais lentas |
| Uso de memória | O cache do WiredTiger exige o carregamento de páginas de índice | Deterioração do desempenho das consultas quando o índice não está em cache |
| Custos de manutenção | Operações como reindexação, monitoramento e reconstrução | Quanto mais índices houver, mais complexa se torna a manutenção |
Compromissos relacionados a índices:
- ✅ Campos consultados com frequência → Criar um índice
- ⚠️ Campos que são atualizados com frequência → Tenha cuidado ao criar índices
- ⚠️ Campos de matriz grandes → os índices com várias chaves podem ficar muito grandes
Estratégia de seleção de índices:
| Cenário | Recomendação | Motivo |
|---|---|---|
| Pesquisa de produtos no comércio eletrônico | Índice composto de categoria + preço | Filtragem e classificação de alta frequência |
| Login do usuário | Índice único de e-mail | Equi-join + restrição de unicidade |
| Consulta de log | Índice de TTL de createdAt | Intervalo de tempo + expiração automática |
| Campo de status (baixa seletividade) | Não é recomendável criá-lo separadamente | isActive tem apenas 2 valores, portanto a eficiência da indexação é extremamente baixa |
| Pesquisa de texto | Índice de texto | Apenas para pesquisa de texto completo |
9. Treinamento prático abrangente
(1) Princípios de projeto de índices
Explicação detalhada da regra ESR: A ordem dos campos em um índice composto deve seguir a sequência Equalidade → Sorteio → Range. Coloque o campo de filtro de igualdade em primeiro lugar (para restringir rapidamente o intervalo), o campo de ordenação no meio (para aproveitar a ordem de classificação do índice e evitar a ordenação na memória) e o campo de consulta de intervalo por último (já que uma varredura de intervalo interromperá o uso dos campos de índice subsequentes).
graph LR
E["Equality<br/>Equivalence Filtering<br/>category='A'"] --> S["Sort<br/>Sort<br/>createdAt: -1"]
S --> R["Range<br/>Scope<br/>price >= 100"]
style E fill:#d4edda
style S fill:#cce5ff
style R fill:#fff3cd
| Ordem do índice | Eficiência da consulta | Motivo |
|---|---|---|
{E, S, R} |
⭐⭐⭐ Ótimo | Correspondência exata → Classificação por índice → Varredura de intervalo |
{E, R, S} |
⭐⭐ Bom | Posicionamento por equivalência → Varredura de intervalo → Classificação por memória |
{R, S, E} |
⭐ Ruim | A varredura de intervalo é muito ampla, o que resulta na perda da vantagem dos isovalores |
// === Design of an E-commerce Product Index ===
db.products.createIndex({ sku: 1 }, { unique: true }); // Unique Index
db.products.createIndex({ category: 1, price: -1 }); // Category Page
db.products.createIndex({ category: 1, rating: -1 }); // Categories+Rating
db.products.createIndex({ isActive: 1, createdAt: -1 }); // Listing Date
db.products.createIndex({ title: 'text', description: 'text' }); // Full-Text Search
db.products.createIndex({ tags: 1 }); // Tag Filtering
(2) Análise do uso de índices
// === Analyzing Slow Queries ===
db.products.find({
category: 'Electronics',
price: { $gte: 100, $lte: 1000 },
isActive: true
}).sort({ createdAt: -1 }).limit(20);
// Check whether an index is being used
const explain = db.products.find({...}).explain('executionStats');
print('Stage:', explain.queryPlanner.winningPlan.stage);
print('Docs Examined:', explain.executionStats.totalDocsExamined);
print('Time:', explain.executionStats.executionTimeMillis, 'ms');
▶ Exemplo: Projeto prático de índices e análise de desempenho
// 1. Create a Test Set(10 10,000 product records)
for (let i = 0; i < 100000; i++) {
db.products.insertOne({
sku: 'SKU-' + i.toString().padStart(6, '0'),
title: 'Product ' + i,
category: ['Electronics', 'Books', 'Clothing', 'Home'][i % 4],
price: Math.random() * 1000,
stock: Math.floor(Math.random() * 100),
createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
isActive: true
});
}
// 2. Create an Index
db.products.createIndex({ sku: 1 }, { unique: true });
db.products.createIndex({ category: 1, price: -1 }); // Composite Index
db.products.createIndex({ createdAt: -1 });
// 3. Comparison:Indexed vs No index
console.time('Unindexed Query');
db.products.find({ category: 'Electronics', price: { $gte: 100, $lte: 500 } }).toArray();
console.timeEnd('Unindexed Query'); // ~500ms
// After creating the index
db.products.createIndex({ category: 1, price: 1 });
console.time('Indexed queries');
db.products.find({ category: 'Electronics', price: { $gte: 100, $lte: 500 } }).toArray();
console.timeEnd('Indexed queries'); // ~5ms(Performance ↑100x)
// 4. explain() Analyze the Execution Plan
const explain = db.products.find({
category: 'Electronics',
price: { $gte: 100, $lte: 500 }
}).sort({ createdAt: -1 }).limit(20).explain('executionStats');
print('Stage:', explain.queryPlanner.winningPlan.stage); // IXSCAN
print('Index:', explain.queryPlanner.winningPlan.inputStage?.indexName); // category_1_price_1
print('Docs Examined:', explain.executionStats.totalDocsExamined);
print('Keys Examined:', explain.executionStats.totalKeysExamined);
print('Returned:', explain.executionStats.nReturned);
print('Time:', explain.executionStats.executionTimeMillis, 'ms');
// 5. Index-Covered Queries(No need to return to the table)
db.products.createIndex({ category: 1, sku: 1, price: 1 });
db.products.find(
{ category: 'Electronics' },
{ sku: 1, price: 1, _id: 0 } // Return only fields that are already in the index
).explain();
// Stage: PROJECTION_COVERED(No need to consult the documentation)
Resultado: O índice melhora o desempenho da consulta em 100 vezes;
EXPLAIN()mostraIXSCAN(varredura de índice) +PROJECTION_COVERED(cobertura de índice).
❓ Perguntas Frequentes
P: É melhor ter mais índices? R: Não. Os índices aceleram as leituras, mas tornam as gravações mais lentas e ocupam espaço. Geralmente, 5 a 10 por coleção é o número adequado.
P: A ordem dos campos em um índice composto é importante? R: É muito importante. Coloque os campos com alta seletividade (muitos valores únicos) no início, seguindo o princípio ESR (Igualdade → Classificação → Intervalo).
P: Por que o índice não está funcionando? R: Causas comuns: (1) A ordem dos campos não corresponde ao prefixo mais à esquerda; (2) Foi usado $ne, $nin ou $exists; (3) Os tipos de dados não correspondem; (4) A coleção contém poucos registros (< 100 registros não são otimizados).
P: Como é possível otimizar o COLLSCAN no
explain()? R: Analise as condições da consulta e crie índices nas colunas de filtragem. Considere o uso de índices compostos que abranjam todas as condições de filtragem.
📖 Resumo
- Princípio de indexação: estrutura de dados B-tree, consultas O(log N)
- sintaxe do
createIndex: campo único, composto, exclusivo, texto - Interpretação da função explain(): winningPlan + executionStats
- O Princípio do Prefixo Mais à Esquerda para Índices Compostos
- Cobertura do índice: retorna resultados diretamente do índice, evitando a consulta a uma tabela
- Sobrecarga do índice: gravações lentas + ocupa espaço
📝 Exercícios
- Questão básica (⭐): Crie três índices — sku, category e createdAt — para a coleção de produtos.
- Pergunta básica (⭐): Use
explain()para analisar uma consulta e confirmar se um índice está sendo utilizado (IXSCAN). - Exercício avançado (⭐⭐): Crie um índice composto { categoria: 1, preço: -1 } para testar o princípio do prefixo mais à esquerda (que consultas utilizam o índice).
- Problema avançado (⭐⭐): Implemente uma consulta coberta por índice (em que todos os campos estejam incluídos no índice).
- Questão de desafio (⭐⭐⭐): Elabore um esquema completo de indexação para produtos de comércio eletrônico (8 índices) e analise os cenários de consulta para cada índice.