Skip to content
On this page

MongoDB 索引优化

索引是MongoDB性能优化的关键组件,它们能够显著提高查询性能。本章详细介绍MongoDB索引的类型、创建、使用和优化策略。

索引基础概念

索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或字段集的值,并按字段值排序,从而允许高效地执行相等匹配和范围查询。

索引的工作原理

javascript
// 无索引时的查询
db.users.find({ email: "user@example.com" })
// MongoDB需要扫描整个集合

// 有索引时的查询
db.users.createIndex({ email: 1 })
db.users.find({ email: "user@example.com" })
// MongoDB使用索引快速定位文档

索引类型

单字段索引

javascript
// 创建单字段升序索引
db.users.createIndex({ email: 1 })

// 创建单字段降序索引
db.users.createIndex({ createdAt: -1 })

// 单字段索引支持的查询
db.users.find({ email: "user@example.com" })  // 高效
db.users.find({ email: { $gt: "a" } })        // 高效
db.users.find({ email: { $in: ["a", "b"] } }) // 高效

复合索引

javascript
// 创建复合索引
db.users.createIndex({ status: 1, age: -1 })

// 复合索引支持的查询
db.users.find({ status: "active" })                    // 高效
db.users.find({ status: "active", age: { $gte: 18 } }) // 高效
db.users.find({ status: "active", age: 25 })           // 高效

// 不支持的查询(前缀不匹配)
db.users.find({ age: 25 })  // 无法使用索引

多键索引

javascript
// 为数组字段创建索引
db.users.createIndex({ tags: 1 })

// 查询数组字段
db.users.find({ tags: "developer" })  // 高效
db.users.find({ tags: { $in: ["developer", "manager"] } })  // 高效

文本索引

javascript
// 创建文本索引
db.articles.createIndex({ content: "text", title: "text" })

// 或者创建通配符文本索引
db.articles.createIndex({ "$**": "text" })

// 文本搜索查询
db.articles.find({ $text: { $search: "mongodb database" } })

// 文本搜索带权重
db.articles.createIndex({
  title: "text",
  content: "text"
}, {
  weights: {
    title: 10,
    content: 5
  }
})

地理空间索引

javascript
// 2dsphere索引(用于球面几何)
db.places.createIndex({ location: "2dsphere" })

// 2d索引(用于平面几何)
db.places.createIndex({ location: "2d" })

// 地理空间查询
db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [116.397428, 39.90923]
      },
      $maxDistance: 1000
    }
  }
})

哈希索引

javascript
// 创建哈希索引(仅支持精确匹配)
db.users.createIndex({ userId: "hashed" })

// 哈希索引只支持相等匹配
db.users.find({ userId: 12345 })  // 高效
// 不支持范围查询

索引管理

创建索引

javascript
// 基本索引创建
db.users.createIndex({ email: 1 })

// 创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

// 创建稀疏索引
db.users.createIndex({ phoneNumber: 1 }, { sparse: true })

// 创建TTL索引(自动删除过期文档)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

// 后台创建索引(避免阻塞数据库)
db.users.createIndex({ profile: 1 }, { background: true })

查看索引信息

javascript
// 查看集合的所有索引
db.users.getIndexes()

// 查看索引大小
db.users.totalIndexSize()

// 查看索引统计信息
db.users.aggregate([{ $indexStats: {} }])

删除索引

javascript
// 删除特定索引
db.users.dropIndex({ email: 1 })

// 删除所有索引(保留_id索引)
db.users.dropIndexes()

// 通过索引名称删除
db.users.dropIndex("email_1")

索引优化策略

选择合适的索引

javascript
// 查询模式分析
// 如果经常查询status和age
db.users.find({ status: "active", age: { $gte: 18 } })

// 应该创建以下索引
db.users.createIndex({ status: 1, age: 1 })

// 而不是
db.users.createIndex({ age: 1, status: 1 })  // 效率较低

复合索引字段顺序

javascript
// 等值查询字段应放在前面
db.users.createIndex({ status: 1, age: 1, createdAt: -1 })

// 范围查询字段应放在后面
db.users.find({
  status: "active",      // 等值
  age: { $gte: 18 },    // 范围
  createdAt: { $gte: new Date("2023-01-01") }  // 范围
})

覆盖查询优化

javascript
// 索引
db.users.createIndex({ status: 1, name: 1 })

// 覆盖查询(只使用索引,不访问文档)
db.users.find(
  { status: "active" },
  { name: 1, status: 1, _id: 0 }  // 只返回索引字段
)

索引性能分析

使用explain()分析查询

javascript
// 分析查询执行计划
db.users.find({ email: "user@example.com" }).explain("executionStats")

// 重要指标:
// - stage: "IXSCAN" 表示使用索引扫描
// - stage: "COLLSCAN" 表示全表扫描
// - totalDocsExamined: 检查的文档数
// - totalDocsReturned: 返回的文档数
// - executionTimeMillis: 执行时间

索引使用情况分析

javascript
// 查看索引使用统计
db.aggregate([
  { $indexStats: {} }
])

// 查找未使用的索引
db.setProfilingLevel(2, { slowms: 0 })
// 运行应用程序一段时间后...
db.system.profile.distinct("ns").forEach(function(ns) {
  if (ns.startsWith("your_database")) {
    printjson(db.runCommand("aggregate", ns.split(".")[1], {
      pipeline: [{$indexStats: {}}]
    }))
  }
})

高级索引技术

部分索引

javascript
// 只为满足条件的文档创建索引
db.users.createIndex(
  { accountBalance: 1 },
  { 
    partialFilterExpression: { 
      accountStatus: "active" 
    } 
  }
)

// 只为特定值的文档创建索引
db.events.createIndex(
  { timestamp: 1 },
  {
    partialFilterExpression: {
      status: { $ne: "deleted" }
    }
  }
)

稀疏索引

javascript
// 只为有该字段的文档创建索引
db.users.createIndex(
  { phoneNumber: 1 },
  { sparse: true }
)

// 稀疏唯一索引
db.users.createIndex(
  { phoneNumber: 1 },
  { 
    unique: true,
    sparse: true
  }
)

数组字段索引

javascript
// 多键索引
db.users.createIndex({ tags: 1 })

// 为嵌套文档数组创建索引
db.users.createIndex({ "hobbies.name": 1 })

// 为数组元素创建索引
db.users.createIndex({ "grades.grade": 1 })

索引最佳实践

索引创建策略

javascript
// 1. 分析查询模式
// 记录应用程序中常用的查询

// 2. 优先创建高选择性字段的索引
// 选择性 = 不同值的数量 / 总文档数
db.users.distinct("status").length / db.users.count()  // 低选择性
db.users.distinct("email").length / db.users.count()   // 高选择性

// 3. 避免过度索引
// 每个索引都会增加写操作的开销

索引监控

javascript
// 监控索引使用情况
db.currentOp({
  "ns": "your_database.users",
  "waitingForLock": true
})

// 查看索引命中率
db.serverStatus().indexCounters

索引维护

javascript
// 重建索引(在后台)
db.users.reIndex()

// 压缩索引
db.runCommand({
  compact: "users",
  indexes: ["email_1", "status_age_1"]
})

索引性能优化案例

案例1:用户查询优化

javascript
// 问题:用户列表查询很慢
db.users.find({
  status: "active",
  age: { $gte: 18, $lte: 65 },
  city: "北京"
})

// 解决方案:创建复合索引
db.users.createIndex({ 
  status: 1, 
  city: 1, 
  age: 1 
})

案例2:分页查询优化

javascript
// 问题:深度分页性能差
db.users.find().skip(10000).limit(10)  // 性能差

// 解决方案:使用范围查询
// 首先按_id排序
db.users.createIndex({ createdAt: 1, _id: 1 })

// 然后使用范围查询
const lastId = ObjectId("...")  // 上一页最后一个文档的_id
db.users.find({ 
  createdAt: { $gte: lastCreatedAt },
  _id: { $gt: lastId }
}).sort({ createdAt: 1, _id: 1 }).limit(10)

案例3:文本搜索优化

javascript
// 创建优化的文本索引
db.articles.createIndex(
  { 
    title: "text",
    content: "text",
    tags: "text"
  },
  {
    weights: {
      title: 10,
      content: 5,
      tags: 3
    },
    name: "text_index",
    default_language: "chinese",  // 如果有中文内容
    language_override: "language"
  }
)

// 使用文本搜索
db.articles.find(
  { $text: { $search: "mongodb performance" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

索引陷阱和注意事项

1. 复合索引字段顺序

javascript
// 错误:范围查询字段在前
db.users.createIndex({ age: 1, status: 1 })  // 低效
db.users.find({ age: { $gte: 18 }, status: "active" })

// 正确:等值查询字段在前
db.users.createIndex({ status: 1, age: 1 })  // 高效

2. 索引过多

javascript
// 避免为每个查询创建索引
// 考虑索引的使用频率和写操作开销

3. 索引大小

javascript
// 监控索引大小
db.users.stats().indexSizes
// 确保索引能够放入内存

总结

MongoDB索引优化是一个持续的过程,需要根据应用程序的查询模式不断调整。合理的索引策略可以显著提高查询性能,但过多的索引会影响写性能。在设计索引时,应该:

  1. 分析查询模式,优先为高频查询创建索引
  2. 使用复合索引时注意字段顺序
  3. 定期监控索引使用情况和性能
  4. 使用部分索引和稀疏索引来减少索引大小
  5. 避免过度索引,平衡读写性能

通过遵循这些最佳实践,可以构建高性能的MongoDB应用程序。