Skip to content

MongoDB 索引类型选择

基本索引类型

单字段索引

单字段索引是最基本的索引类型,基于单个字段创建索引。

特点

  • 支持升序(1)和降序(-1)
  • 适合基于单个字段的查询、排序和范围查询
  • 创建和维护成本较低

适用场景

  • 频繁基于单个字段进行查询
  • 需要对单个字段进行排序
  • 范围查询(如 age > 30

创建示例

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

// 创建降序索引
db.users.createIndex({ created_at: -1 })

复合索引

复合索引基于多个字段创建索引,字段顺序对查询性能有重要影响。

特点

  • 支持多个字段的组合查询
  • 字段顺序遵循最左前缀原则
  • 适合覆盖查询(包含查询所需的所有字段)

适用场景

  • 频繁基于多个字段的组合进行查询
  • 需要对多个字段进行排序
  • 覆盖查询场景

创建示例

javascript
// 创建复合索引
db.orders.createIndex({ customer_id: 1, order_date: -1 })

// 最左前缀原则:该索引支持 { customer_id }, { customer_id, order_date } 查询
// 但不支持 { order_date } 查询

多键索引

多键索引用于数组字段,MongoDB 会为数组中的每个元素创建索引项。

特点

  • 自动为数组字段创建
  • 支持基于数组元素的查询
  • 支持数组字段的排序

适用场景

  • 数组字段的查询(如 tags: "mongodb"
  • 数组字段的范围查询
  • 数组字段的排序

创建示例

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

// 查询示例
db.products.find({ tags: "database" })

地理空间索引

地理空间索引用于存储和查询地理空间数据,如经纬度坐标。

类型

  • 2d 索引:用于平面坐标
  • 2dsphere 索引:用于地球表面的球体坐标
  • geoHaystack 索引:用于高基数地理空间数据

适用场景

  • 位置查询(如查找附近的商店)
  • 地理围栏查询
  • 距离计算

创建示例

javascript
// 创建 2dsphere 索引
db.locations.createIndex({ location: "2dsphere" })

// 查询示例:查找距离指定点 10 公里内的位置
db.locations.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [116.4074, 39.9042]  // 北京坐标
      },
      $maxDistance: 10000
    }
  }
})

文本索引

文本索引用于全文搜索,支持对字符串内容进行搜索。

特点

  • 支持多种语言
  • 支持短语搜索、前缀搜索
  • 支持权重设置

适用场景

  • 全文搜索(如博客文章内容搜索)
  • 基于文本内容的过滤
  • 支持多种语言的搜索

创建示例

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

// 设置权重
db.articles.createIndex(
  { content: "text", title: "text" },
  { weights: { title: 10, content: 1 } }
)

// 查询示例
db.articles.find({ $text: { $search: "mongodb index" } })

哈希索引

哈希索引基于字段的哈希值创建索引,适合等值查询。

特点

  • 只支持等值查询,不支持范围查询和排序
  • 哈希值均匀分布,适合分片键
  • 不支持前缀匹配

适用场景

  • 等值查询(如 user_id: "12345"
  • 作为分片键使用

创建示例

javascript
// 创建哈希索引
db.users.createIndex({ user_id: "hashed" })

// 查询示例
db.users.find({ user_id: "12345" })

稀疏索引

稀疏索引只包含有索引字段的文档,跳过没有该字段的文档。

特点

  • 减少索引大小
  • 提高查询性能
  • 只包含有索引字段的文档

适用场景

  • 索引字段不是所有文档都包含
  • 减少索引存储空间
  • 提高查询效率

创建示例

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

// 查询示例
db.users.find({ email: { $exists: true } })

唯一索引

唯一索引确保索引字段的值在集合中是唯一的。

特点

  • 防止重复数据
  • 可以基于单个或多个字段
  • 支持稀疏唯一索引

适用场景

  • 确保数据唯一性(如用户邮箱)
  • 防止重复插入
  • 作为主键使用

创建示例

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

// 创建复合唯一索引
db.orders.createIndex({ order_no: 1, customer_id: 1 }, { unique: true })

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

部分索引

部分索引只包含满足指定过滤条件的文档,减少索引大小。

特点

  • 基于过滤条件创建
  • 减少索引大小和维护成本
  • 提高查询性能

适用场景

  • 只需要为部分文档创建索引
  • 过滤条件明确的场景
  • 减少索引存储空间

创建示例

javascript
// 创建部分索引
db.orders.createIndex(
  { total_amount: 1 },
  { partialFilterExpression: { status: "completed" } }
)

// 查询示例
db.orders.find({ status: "completed", total_amount: { $gt: 100 } })

覆盖索引

覆盖索引包含查询所需的所有字段,不需要回表查询文档数据。

特点

  • 提高查询性能
  • 减少磁盘 I/O
  • 适合频繁的查询场景

适用场景

  • 频繁执行的查询
  • 只需要返回少量字段的查询
  • 对性能要求高的场景

创建示例

javascript
// 创建覆盖索引(包含查询所需的所有字段)
db.users.createIndex({ name: 1, email: 1, age: 1 })

// 查询示例:覆盖查询,不需要回表
db.users.find(
  { name: /^A/ },
  { name: 1, email: 1, age: 1, _id: 0 }
)

索引类型选择原则

1. 基于查询模式选择

  • 分析查询模式,确定最频繁的查询类型
  • 优先为频繁查询的字段创建索引
  • 考虑查询的选择性,选择性高的字段适合创建索引

2. 遵循最左前缀原则

  • 复合索引的字段顺序对查询性能有重要影响
  • 将最常用的查询字段放在最左边
  • 将选择性高的字段放在最左边

3. 考虑索引大小和维护成本

  • 索引会增加存储空间和写操作开销
  • 平衡查询性能和索引维护成本
  • 避免创建过多不必要的索引

4. 覆盖查询优先

  • 优先考虑创建覆盖索引,减少回表查询
  • 包含查询所需的所有字段
  • 提高查询性能

5. 考虑数据分布

  • 分析数据分布,选择合适的索引类型
  • 对于高基数字段,考虑使用唯一索引或哈希索引
  • 对于低基数字段,考虑使用部分索引

索引类型选择决策树

常见索引类型选择场景

1. 用户管理系统

场景描述

  • 用户登录:基于邮箱和密码查询
  • 用户列表:基于名称、创建时间排序
  • 用户搜索:基于名称、邮箱的模糊搜索

索引选择

javascript
// 登录查询:唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

// 用户列表:复合索引
db.users.createIndex({ created_at: -1, name: 1 })

// 用户搜索:文本索引
db.users.createIndex({ name: "text", email: "text" })

2. 电商订单系统

场景描述

  • 订单查询:基于用户 ID 和订单日期
  • 订单状态:基于状态和创建时间
  • 订单金额:基于金额范围查询

索引选择

javascript
// 订单查询:复合索引
db.orders.createIndex({ user_id: 1, order_date: -1 })

// 订单状态:部分索引
db.orders.createIndex(
  { status: 1, created_at: -1 },
  { partialFilterExpression: { status: { $in: ["pending", "processing"] } } }
)

// 订单金额:复合索引
db.orders.createIndex({ user_id: 1, total_amount: 1 })

3. 内容管理系统

场景描述

  • 文章查询:基于分类、发布时间
  • 文章搜索:基于标题、内容的全文搜索
  • 标签查询:基于标签数组

索引选择

javascript
// 文章查询:复合索引
db.articles.createIndex({ category_id: 1, published_at: -1 })

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

// 标签查询:多键索引
db.articles.createIndex({ tags: 1 })

索引类型性能比较

索引类型查询性能写操作开销存储空间适用场景
单字段索引单个字段的等值、范围查询
复合索引多个字段的组合查询
多键索引数组字段的查询
文本索引全文搜索
地理空间索引地理空间查询
哈希索引等值查询、分片键
唯一索引确保数据唯一性
稀疏索引字段不是必填的场景
部分索引只需要部分文档的场景
覆盖索引极高覆盖查询场景

索引类型选择最佳实践

1. 分析查询模式

  • 使用 db.collection.explain() 分析查询执行计划
  • 使用 db.currentOp() 查看当前运行的查询
  • 使用 system.profile 收集慢查询日志

2. 避免过度索引

  • 每个集合的索引数量不宜过多(建议不超过 10 个)
  • 定期清理不必要的索引
  • 平衡查询性能和写操作开销

3. 监控索引使用情况

  • 使用 db.collection.stats() 查看索引大小和使用情况
  • 使用 db.serverStatus().indexCounters 查看索引计数器
  • 使用 db.collection.aggregate([{ $indexStats: {} }]) 查看索引使用统计

4. 考虑数据增长

  • 预估数据增长趋势,选择合适的索引类型
  • 对于大数据集,考虑使用分片和合适的分片键
  • 定期重建索引,优化索引性能

5. 测试和验证

  • 在测试环境中验证索引性能
  • 使用真实数据进行性能测试
  • 定期回顾和优化索引策略

常见问题(FAQ)

Q1: 如何选择复合索引的字段顺序?

A1: 复合索引的字段顺序应遵循以下原则:

  1. 将最常用的查询字段放在最左边
  2. 将选择性高的字段放在最左边
  3. 考虑排序和范围查询的需求
  4. 遵循最左前缀原则

Q2: 什么时候应该使用部分索引?

A2: 当只需要为集合中的部分文档创建索引时,应使用部分索引。例如:

  • 只需要为状态为 "active" 的文档创建索引
  • 只需要为金额大于 100 的订单创建索引
  • 只需要为特定类型的文档创建索引

Q3: 如何选择分片键的索引类型?

A3: 选择分片键的索引类型应考虑:

  1. 等值查询场景:使用哈希索引
  2. 范围查询场景:使用单字段或复合索引
  3. 数据分布均匀性:确保分片键的值分布均匀
  4. 查询模式:考虑最频繁的查询类型

Q4: 什么时候应该使用覆盖索引?

A4: 当查询只需要返回少量字段,且这些字段可以被索引覆盖时,应使用覆盖索引。例如:

  • 查询用户的名称和邮箱,不返回其他字段
  • 查询订单的金额和状态,不返回其他字段
  • 频繁执行的统计查询

Q5: 如何处理低基数字段的索引?

A5: 对于低基数字段(如性别、状态),应考虑:

  1. 避免单独创建索引,除非查询频率非常高
  2. 作为复合索引的一部分使用
  3. 考虑使用部分索引,只包含常用的状态值
  4. 分析查询选择性,选择性低于 10% 的字段不适合创建索引

Q6: 如何选择文本索引的权重?

A6: 选择文本索引的权重应考虑:

  1. 将更重要的字段(如标题)设置更高的权重
  2. 根据业务需求调整权重值
  3. 测试不同权重组合的查询效果
  4. 定期回顾和调整权重设置

Q7: 什么时候应该使用地理空间索引?

A7: 当需要存储和查询地理空间数据时,应使用地理空间索引。例如:

  • 存储和查询经纬度坐标
  • 查找附近的地点
  • 地理围栏查询
  • 距离计算

Q8: 如何监控索引的使用情况?

A8: 监控索引使用情况的方法:

  1. 使用 db.collection.aggregate([{ $indexStats: {} }]) 查看索引使用统计
  2. 使用 db.serverStatus().indexCounters 查看索引计数器
  3. 使用 db.collection.stats() 查看索引大小
  4. 使用 MongoDB Compass 或其他监控工具查看索引性能

Q9: 如何优化多键索引的性能?

A9: 优化多键索引性能的方法:

  1. 限制数组元素的数量
  2. 避免在大型数组上创建多键索引
  3. 考虑使用分片,分散数据负载
  4. 定期重建索引,优化索引性能

Q10: 如何选择唯一索引和普通索引?

A10: 选择唯一索引和普通索引的原则:

  1. 当需要确保字段值的唯一性时,使用唯一索引
  2. 当只需要提高查询性能,不需要确保唯一性时,使用普通索引
  3. 唯一索引可以包含空值(最多一个文档)
  4. 考虑使用稀疏唯一索引,跳过没有索引字段的文档