基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

MongoDB的复合索引 (Compound Index)

知识点图片

MongoDB 复合索引 (Compound Index) 是指一个索引结构中包含多个字段。它是 MongoDB 性能优化的核心工具之一。

相比于单字段索引,复合索引可以同时支持多个字段的查询、排序和范围查找,但其设计需要遵循特定的规则。

以下是关于复合索引的详细指南,包括原理、最佳实践(ESR 规则)和常见陷阱。


1. 基本语法

创建复合索引时,需要指定包含的字段及其排序方向(1 为升序,-1 为降序)。

javascript
// 创建一个包含 item 和 stock 字段的复合索引
db.products.createIndex({ "item": 1, "stock": -1 })
  • item: 1: 首先按照 item 字段升序排列。
  • stock: -1: 在 item 相同的情况下,按照 stock 字段降序排列。

2. 核心概念

A. 字段顺序至关重要 (Field Order)

复合索引中字段的顺序决定了索引的结构。

  • 例子: 索引 { a: 1, b: 1 }
  • 结构: 数据首先按 a 排序;只有当 a 的值相同时,才按 b 排序。
  • 影响: 如果你的查询是 find({ b: "value" }),这个索引是无效的,因为你跳过了 a

B. 索引前缀 (Index Prefixes)

MongoDB 可以使用复合索引的“前缀”来支持查询。
假设索引为:{ a: 1, b: 1, c: 1 }

  • 支持的查询:
    • { a: ... }
    • { a: ..., b: ... }
    • { a: ..., b: ..., c: ... }
  • 不支持的查询 (无法利用索引):
    • { b: ... } (跳过了头部)
    • { c: ... }
    • { b: ..., c: ... }

C. 排序方向 (Sort Order)

对于单字段索引,排序方向(1 或 -1)不重要,因为 MongoDB 可以双向遍历。
但在复合索引中,排序方向的组合非常重要。

假设索引:{ a: 1, b: -1 } (a 升序,b 降序)

  • 支持的排序:
    • sort({ a: 1, b: -1 }) (完全匹配)
    • sort({ a: -1, b: 1 }) (完全反向,MongoDB 可以反向读取索引)
  • 不支持的排序 (需要内存排序,性能差):
    • sort({ a: 1, b: 1 })
    • sort({ a: -1, b: -1 })

3. 最佳实践:ESR 规则 (黄金法则)

设计复合索引时,应遵循 ESR 顺序原则,这能最大化索引利用率:

  1. E (Equality) - 精确匹配:
    • 放在索引的最前面。
    • 例如:find({ status: "active" }) 中的 status
  2. S (Sort) - 排序:
    • 放在中间。
    • 这允许 MongoDB 在扫描索引时直接按需要的顺序获取数据,避免昂贵的内存排序 (Blocking Sort)。
  3. R (Range) - 范围查询:
    • 放在最后。
    • 例如:$gt, $lt, $gte
    • 一旦使用了范围查询,索引中后续的字段就无法用于高效排序了。

ESR 示例场景

假设集合 students,查询:“查找班级为 1 班,分数大于 80 的学生,并按最后更新时间排序”

javascript
db.students.find({ class_id: 1, score: { $gt: 80 } }).sort({ last_updated: 1 })
  • Equality: class_id (精确匹配 1)
  • Sort: last_updated (需要排序)
  • Range: score (范围 > 80)

最佳索引设计: { class_id: 1, last_updated: 1, score: 1 }

分析:

  1. 先定位到 class_id = 1 的区域。
  2. 在该区域内,数据已经按 last_updated 排好序了。
  3. MongoDB 只需要扫描索引,剔除 score <= 80 的条目即可。

错误设计: { class_id: 1, score: 1, last_updated: 1 }

  • 如果把 Range (score) 放在 Sort (last_updated) 之前,MongoDB 虽然能快速找到分数大于 80 的记录,但这些记录在索引中并不是last_updated 排序的。因此 MongoDB 必须在内存中重新排序结果,这在数据量大时会导致性能骤降。

4. 复合索引 vs 多个单字段索引

MongoDB 有一种机制叫 索引交集 (Index Intersection),即一个查询可以使用两个单字段索引。

  • 查询: find({ a: 1, b: 1 })
  • 现有索引: Index A { a: 1 }, Index B { b: 1 }
  • 结果: MongoDB 可以 分别扫描 A 和 B,然后计算交集。
  • 结论: 虽然可行,但性能通常远不如复合索引 { a: 1, b: 1 }。复合索引只需要扫描一次。

5. 限制与注意事项

  1. 字段数量限制: 一个复合索引最多包含 32 个字段。
  2. 写入性能: 索引越多,插入/更新/删除操作越慢,因为每次数据变动都需要更新索引。不要创建不必要的索引。
  3. 覆盖查询 (Covered Queries): 如果一个复合索引包含了查询中需要返回的所有字段(Projection),MongoDB 可以直接从索引返回结果,而无需查找实际文档(回表),性能极高。
    • 例子: 索引 { name: 1, age: 1 },查询 db.users.find({ name: "Alice" }, { _id: 0, name: 1, age: 1 })
  4. 哈希索引: 复合索引中不能包含哈希索引字段(Hashed Index)。

总结

设计 MongoDB 复合索引时,请以此为清单:

  1. 查询模式优先: 根据实际的查询语句(Find + Sort)来设计。
  2. 遵循 ESR: 精确匹配 -> 排序 -> 范围查询。
  3. 注意前缀: 确保索引的字段顺序能覆盖尽可能多的查询场景。
  4. 检查排序方向: 如果涉及多字段排序,方向必须与索引定义一致(或完全相反)。
00:00
00:00