MongoDB索引
一、索引简介
1.1 索引的概念
- 索引 是一种特殊的数据结构,存储在内存中,用于提高查询效率。
- MongoDB 中索引使用 B-Tree 结构(默认),支持高效的范围查询和排序。
- 索引类似于书籍的目录,能快速定位到数据所在位置,避免全表扫描。
1.2 索引的作用
- 加速查询:尤其是对大量数据的查询、排序、分组等操作。
- 唯一性约束:通过唯一索引保证字段值不重复。
- 覆盖查询:当查询的字段全部在索引中时,可以直接从索引返回结果,无需访问文档。
1.3 索引的代价
- 占用存储空间:每个索引都会占用额外的磁盘和内存空间。
- 影响写入性能:插入、更新、删除操作时,需要同步维护索引,导致写操作变慢。
- 增加内存压力:索引应尽量常驻内存,否则会影响性能。
二、explain输出
2.1 explain() 方法
- 用于分析查询的执行计划,帮助判断查询是否使用了索引。
- 语法:
db.collection.find().explain("executionStats")
2.2 explain 的三种模式
| 模式 | 说明 |
|---|---|
queryPlanner |
只返回查询计划,不实际执行 |
executionStats |
返回查询计划并执行,显示执行统计信息 |
allPlansExecution |
返回所有候选执行计划的详细信息 |
2.3 关键输出字段解读
| 字段 | 含义 |
|---|---|
stage |
执行阶段,如 COLLSCAN(全表扫描)、IXSCAN(索引扫描)、FETCH(回表读取文档) |
nReturned |
返回的文档数量 |
totalDocsExamined |
扫描的文档总数 |
totalKeysExamined |
扫描的索引条目数 |
indexName |
使用的索引名称 |
winningPlan |
最终选择的执行计划 |
2.4 性能分析原则
- 理想情况 :
totalKeysExamined ≈ nReturned,且使用IXSCAN。 - 需优化情况 :
totalDocsExamined远大于nReturned,说明未有效使用索引。
三、何时不使用索引
3.1 不适合使用索引的场景
| 场景 | 原因 |
|---|---|
| 集合很小 | 全表扫描比索引查找更快,索引反而增加开销 |
| 查询返回大部分数据 | 索引+回表可能比全表扫描更慢 |
| 频繁更新的字段 | 索引维护成本高 |
| 低基数字段 | 如性别、布尔值等,索引区分度低,效率提升有限 |
| 查询使用正则表达式(非前缀) | 如 /.*abc/ 无法利用索引 |
| 大量写操作场景 | 索引会显著降低写入性能 |
3.2 如何判断是否使用索引
- 使用
explain()分析执行计划。 - 如果
stage为COLLSCAN,说明未使用索引。 - 结合业务读写比例,权衡是否建立索引。
四、索引类型
4.1 单字段索引
- 最基础的索引类型,针对单个字段建立。
- 示例:
db.users.createIndex({name: 1})
4.2 复合索引
- 针对多个字段建立的索引,顺序非常重要。
- 遵循 ESR 原则:等值查询(Equality) → 排序字段(Sort) → 范围查询(Range)
- 示例:
db.users.createIndex({age: 1, name: -1})
4.3 多键索引
- 用于数组字段,MongoDB 自动为数组每个元素创建索引。
- 示例:
db.articles.createIndex({tags: 1})
4.4 唯一索引
- 确保字段值唯一,不允许重复。
- 示例:
db.users.createIndex({email: 1}, {unique: true})
4.5 稀疏索引
- 只索引包含该字段的文档,适用于字段缺失较多的集合。
- 示例:
db.users.createIndex({email: 1}, {sparse: true})
4.6 文本索引
- 支持字符串内容的全文检索,可对多个字段建立。
- 示例:
db.articles.createIndex({content: "text", title: "text"})
4.7 地理空间索引
- 支持地理坐标查询,如
2dsphere(球面)和2d(平面)。 - 示例:
db.places.createIndex({location: "2dsphere"})
4.8 哈希索引
- 用于分片键,支持等值查询,不支持范围查询。
- 示例:
db.users.createIndex({user_id: "hashed"})
4.9 TTL索引
- 自动删除过期文档,常用于日志、会话数据。
- 示例:
db.sessions.createIndex({createdAt: 1}, {expireAfterSeconds: 3600})
五、索引管理
5.1 创建索引
javascript
// 单字段索引
db.collection.createIndex({field: 1})
// 复合索引
db.collection.createIndex({field1: 1, field2: -1})
// 带选项的索引
db.collection.createIndex({field: 1}, {unique: true, background: true})
5.2 查看索引
javascript
// 查看集合所有索引
db.collection.getIndexes()
// 查看索引大小
db.collection.stats().indexSizes
5.3 删除索引
javascript
// 删除指定索引
db.collection.dropIndex("index_name")
// 删除所有非 _id 索引
db.collection.dropIndexes()
5.4 索引构建方式
| 方式 | 说明 |
|---|---|
background: true |
后台构建,不阻塞读写,但速度较慢(早期版本) |
| 默认(前台) | 阻塞所有读写操作,适合维护窗口执行 |
| 滚动索引构建 | 在副本集中逐个节点构建,减少影响 |
5.5 索引优化建议
- 避免冗余索引 :如已有
{a:1, b:1},则{a:1}是冗余的。 - 使用覆盖查询 :尽量让查询字段全部在索引中,避免
FETCH。 - 监控索引使用情况 :通过
$indexStats或explain()分析未使用的索引。 - 定期重建索引:对于频繁更新的集合,索引碎片可能影响性能。
总结
MongoDB 索引是提升查询性能的关键工具,但并非所有场景都适合使用。合理设计索引需要结合查询模式、数据分布、读写比例等因素。通过 explain() 分析执行计划、选择合适的索引类型、科学管理索引生命周期,可以有效平衡查询性能与系统开销。