索引类型与结构
MongoDB 常规单字段和复合索引使用有序 B-tree 结构。索引保存 Field Value 与 Record Location,用于缩小文档扫描范围或直接提供排序;索引不会自动改善与其键顺序、类型或操作符不匹配的查询。
索引类型
| 类型 | 用途 | 主要边界 |
|---|---|---|
| Single Field | 单字段过滤和排序 | 复合条件可能需要额外过滤 |
| Compound | 多字段过滤、排序和覆盖查询 | 字段顺序决定可用前缀与排序能力 |
| Multikey | 自动索引数组中的元素 | 一个复合索引中每个文档不能有多个被索引的数组字段 |
| Unique | 约束不同文档间键值唯一 | 缺失和 null 的行为需结合 Sparse/Partial 设计 |
| Partial | 只索引满足表达式的文档 | 查询必须包含能够使用该过滤条件的语义 |
| Sparse | 只索引包含目标 Field 的文档 | 与 null、缺失和唯一性组合时容易混淆 |
| TTL | 到期后由后台任务删除文档 | 删除不是精确到期瞬间,不适合严格调度 |
| Wildcard | 索引变化或未知的 Field Path | 不能替代面向真实负载的专用索引 |
| Text | 自托管全文词项检索 | 功能和语言处理有限,与 Atlas Search 不同 |
| 2d / 2dsphere | 平面或 GeoJSON 地理查询 | 坐标格式和支持操作符不同 |
| Hashed | 支持哈希分片和等值分布 | 不保留范围顺序 |
| Clustered Collection | 按 Clustered Key 组织记录 | 与普通 Secondary Index 的物理语义不同 |
复合索引顺序
常用思路是 ESR:Equality、Sort、Range,但它是启发式规则,不是机械公式。
javascript
db.orders.createIndex({ tenantId: 1, status: 1, createdAt: -1 })该索引适合固定 tenantId、status 后按 createdAt 倒序读取。仅查询 status 通常不能有效使用最左前缀;对 createdAt 使用范围后,后续键能否继续缩小扫描也会受到限制。
复合索引的升降序只有在支持排序时才真正重要。查询能够按索引整体方向或整体反向扫描,但混合排序方向必须与索引键方向匹配。
Multikey
对数组 Field 建索引后会自动成为 Multikey Index,一个文档可能为多个数组元素生成索引项:
javascript
db.users.createIndex({ tags: 1 })数组越长,索引写入、空间和扫描成本越高。查询同一个数组元素内的多个约束时,通常需要 $elemMatch 让语义和 Index Bound 正确组合。
Covered Query
如果 Filter 和 Projection 所需字段都能从索引获得,执行计划可能无需读取完整文档。是否覆盖还受 _id 是否被返回、Multikey 路径、字段存在性和具体查询操作符影响。
覆盖索引会减少文档读取,但把过多 Field 加入索引会扩大 Cache 占用和写放大。索引设计应围绕稳定高频查询,而不是为每个字段都建立索引。
执行计划与维护
javascript
db.orders.find({ tenantId: 1, status: "paid" })
.sort({ createdAt: -1 })
.explain("executionStats")重点观察:
winningPlan是IXSCAN、COLLSCAN还是需要阻塞排序。totalKeysExamined与totalDocsExamined相对返回数量是否过大。- 是否存在 FETCH 后过滤大量文档。
- Plan Cache 是否因数据分布变化保留了不合适计划。
- 重复或前缀包含的索引能否合并,删除前是否覆盖完整业务周期。
Index Build、隐藏、删除和 Compact 等维护动作会消耗 CPU、I/O、内存及复制带宽,需要按版本能力和业务窗口规划。