2026年Debian上MongoDB索引优化完全指南:7大核心技巧提升查询性能(2026)

一、索引基础

1.1 什么是索引

索引是MongoDB中用于加速查询的数据结构,类似于书籍的目录。没有索引时,MongoDB必须扫描整个集合才能找到匹配的文档(全表扫描),而有了索引,MongoDB可以快速定位到目标文档,大幅减少查询时间。

1.2 索引类型

索引类型 描述 适用场景
单字段索引 对单个字段建立索引 简单查询条件
复合索引 对多个字段建立索引 多条件查询
多键索引 对数组字段建立索引 数组元素查询
哈希索引 对字段值哈希建立索引 等值查询
文本索引 对文本内容建立索引 全文搜索
地理空间索引 对坐标数据建立索引 LBS服务
唯一索引 保证字段值唯一 主键、邮箱等

1.3 索引代价

索引并非越多越好,每个索引都有代价:
写入开销:每次插入/更新/删除都需要更新索引
存储空间:索引占用额外的磁盘空间
内存占用:活跃索引需要常驻内存

二、索引设计原则

2.1 ESR原则

MongoDB复合索引的黄金法则——Equality、Sort、Range:
1. 等值查询字段放前面:精确匹配的条件应放在最前
2. 排序字段放中间:需要排序的字段放在等值之后
3. 范围查询字段放后面:范围查询($gt, $lt等)放在最后

// 查询:status等值 + 按created_at排序 + age范围
// 正确索引
db.users.createIndex({ status: 1, created_at: -1, age: 1 })

// 错误索引(范围查询在排序前)
db.users.createIndex({ status: 1, age: 1, created_at: -1 })

2.2 选择性原则

索引的选择性越高,过滤效果越好:
高选择性:字段值唯一性高(如email、user_id)
低选择性:字段值重复多(如status、gender)

低选择性的字段不应单独建索引,应与其他字段组合。

2.3 覆盖查询

如果查询的所有字段都包含在索引中,MongoDB可以直接从索引返回结果,无需读取文档:

// 创建覆盖索引
db.users.createIndex({ email: 1, name: 1, phone: 1 })

// 覆盖查询(只返回索引包含的字段)
db.users.find(
  { email: "test@example.com" },
  { name: 1, phone: 1, _id: 0 }
).hint({ email: 1, name: 1, phone: 1 })

三、索引创建与优化

3.1 创建单字段索引

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

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

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

// 创建稀疏索引(仅索引存在该字段的文档)
db.users.createIndex({ nickname: 1 }, { sparse: true })

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

// 创建部分索引(仅索引满足条件的文档)
db.orders.createIndex(
  { status: 1, created_at: -1 },
  { partialFilterExpression: { status: { $in: ["active", "pending"] } } }
)

3.2 创建复合索引

// 基本复合索引
db.orders.createIndex({ user_id: 1, created_at: -1 })

// 指定索引名称
db.orders.createIndex(
  { user_id: 1, created_at: -1 },
  { name: "idx_user_orders" }
)

// 后台创建(不阻塞数据库操作)
db.orders.createIndex(
  { user_id: 1, created_at: -1 },
  { background: true }
)

3.3 创建文本索引

// 单字段文本索引
db.articles.createIndex({ content: "text" })

// 多字段文本索引
db.articles.createIndex({
  title: "text",
  content: "text",
  tags: "text"
}, {
  weights: {
    title: 10,
    content: 5,
    tags: 2
  },
  name: "article_text_index"
})

// 全文搜索
db.articles.find({ $text: { $search: "MongoDB优化" } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } })

3.4 创建地理空间索引

// 2d索引(平面坐标)
db.locations.createIndex({ location: "2d" })

// 2dsphere索引(球面坐标)
db.locations.createIndex({ location: "2dsphere" })

// 地理空间查询
db.locations.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [121.4737, 31.2304] },
      $maxDistance: 5000
    }
  }
})

四、索引分析工具

4.1 explain()分析查询

// 查看查询计划
db.users.find({ email: "test@example.com" }).explain()

// 查看执行统计
db.users.find({ email: "test@example.com" }).explain("executionStats")

// 查看详细执行信息
db.users.find({ email: "test@example.com" }).explain("allPlansExecution")

关键字段解读

// executionStats 关键指标
{
  executionTimeMillis: 5,      // 执行时间(ms)
  totalDocsExamined: 1,        // 扫描文档数
  totalKeysExamined: 1,        // 扫描索引键数
  executionStages: {
    stage: "IXSCAN",           // 使用了索引扫描
    indexName: "email_1",      // 使用的索引名
    docsExamined: 1,
    keysExamined: 1
  }
}

4.2 性能指标判断

指标 理想值 说明
stage IXSCAN/FETCH 使用索引扫描
totalKeysExamined ≈ nReturned 接近1:1 索引效率高
totalDocsExamined ≈ nReturned 接近1:1 无多余扫描
executionTimeMillis <100ms 查询速度快
stage = COLLSCAN 全表扫描,需要加索引

4.3 $indexStats分析索引使用

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

// 输出示例
{
  name: "email_1",
  accesses: { ops: 15000, since: ISODate("2026-01-01T00:00:00Z") },
  usageHost: "mongodb-server:27017"
}

4.4 慢查询分析

// 启用慢查询日志(>100ms)
db.setProfilingLevel(1, { slowms: 100 })

// 查看慢查询
db.system.profile.find().sort({ ts: -1 }).limit(10).pretty()

// 查看最慢的查询
db.system.profile.find({
  millis: { $gt: 100 }
}).sort({ millis: -1 }).limit(10)

// 关闭慢查询日志
db.setProfilingLevel(0)

五、索引维护

5.1 查看索引信息

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

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

// 查看索引构建进度
db.currentOp({
  $or: [
    { op: "command", "command.createIndexes": { $exists: true } },
    { op: "none", ns: /mydb/ }
  ]
})

5.2 删除无用索引

// 删除指定索引
db.users.dropIndex("email_1")

// 删除除_id外的所有索引
db.users.dropIndexes()

// 使用$indexStats识别无用索引
db.users.aggregate([{ $indexStats: {} }]).forEach(function(idx) {
  if (idx.accesses.ops === 0) {
    print("未使用索引: " + idx.name);
    // db.users.dropIndex(idx.name);  // 确认后删除
  }
});

5.3 重建索引

// 重建集合所有索引
db.users.reIndex()

// 在副本集中滚动重建索引(不中断服务)
// 1. 在从节点停止
rs.stepDown()

// 2. 在从节点重建
db.users.reIndex()

// 3. 在其他从节点重复

六、高级优化技巧

6.1 部分索引(Partial Index)

仅索引满足条件的文档,减少索引大小:

// 只索引活跃用户
db.users.createIndex(
  { last_login: -1 },
  { partialFilterExpression: { status: "active" } }
)

// 只索引高价订单
db.orders.createIndex(
  { user_id: 1 },
  { partialFilterExpression: { amount: { $gt: 1000 } } }
)

6.2 稀疏索引(Sparse Index)

跳过不包含索引字段的文档:

// 只索引有手机号的用户
db.users.createIndex({ phone: 1 }, { sparse: true })

// 只索引有标签的文章
db.articles.createIndex({ tags: 1 }, { sparse: true })

6.3 隐藏索引(Hidden Index)

隐藏索引但不删除,用于测试删除索引的影响:

// 隐藏索引
db.users.hideIndex("email_1")

// 测试查询性能
db.users.find({ email: "test@example.com" }).explain("executionStats")

// 如果性能无影响,安全删除
db.users.dropIndex("email_1")

// 如果性能下降,恢复索引
db.users.unhideIndex("email_1")

6.4 索引交集优化

MongoDB可以使用多个索引的交集来满足查询:

// 单独索引
db.users.createIndex({ status: 1 })
db.users.createIndex({ created_at: -1 })

// 查询可以使用索引交集
db.users.find({
  status: "active",
  created_at: { $gt: ISODate("2026-01-01") }
}).explain("executionStats")
// 如果使用了AND_SORTED stage,说明使用了索引交集

七、索引优化实战

7.1 电商场景优化

// 商品查询(分类+价格范围+销量排序)
db.products.createIndex({ category: 1, price: 1, sales: -1 })

// 订单查询(用户+时间+状态)
db.orders.createIndex({ user_id: 1, created_at: -1, status: 1 })

// 搜索建议(前缀匹配)
db.products.createIndex({ name: 1 })

// 库存更新(SKU精确匹配)
db.inventory.createIndex({ sku: 1 }, { unique: true })

7.2 日志场景优化

// 日志查询(时间范围+级别)
db.logs.createIndex({ timestamp: -1, level: 1 })

// TTL索引自动清理
db.logs.createIndex({ timestamp: 1 }, { expireAfterSeconds: 2592000 }) // 30天

// 部分索引(只索引ERROR和WARN)
db.logs.createIndex(
  { timestamp: -1, level: 1 },
  { partialFilterExpression: { level: { $in: ["ERROR", "WARN"] } } }
)

7.3 社交场景优化

// 用户动态(用户+时间)
db.posts.createIndex({ user_id: 1, created_at: -1 })

// 好友关系(双向查询)
db.friends.createIndex({ user_id: 1, friend_id: 1 })
db.friends.createIndex({ friend_id: 1, user_id: 1 })

// 附近的人(地理空间)
db.users.createIndex({ location: "2dsphere" })

八、监控与告警

8.1 索引使用率监控脚本

#!/bin/bash
# /usr/local/bin/index-monitor.sh

DB_NAME="mydb"
OUTPUT="/tmp/index-report-$(date +%Y%m%d).txt"

echo "=== 索引使用率报告 - $(date) ===" > $OUTPUT

mongosh $DB_NAME --quiet --eval "
  db.getCollectionNames().forEach(function(col) {
    print('\n--- ' + col + ' ---');
    db[col].aggregate([{\$indexStats: {}}]).forEach(function(idx) {
      var ratio = idx.accesses.ops > 0 ? 'USED' : 'UNUSED';
      print(idx.name + ' | ' + idx.accesses.ops + ' ops | ' + ratio);
    });
  });
" >> $OUTPUT

cat $OUTPUT

8.2 Prometheus监控指标

mongodb_index_accesses_total{collection="users", index="email_1"}
mongodb_index_size_bytes{collection="users", index="email_1"}
mongodb_collection_scan_ratio{collection="users"}

8.3 告警规则

# Prometheus告警规则
groups:
  - name: mongodb_index
    rules:
      - alert: MongoDBHighCollScan
        expr: rate(mongodb_collection_scan_ratio[5m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MongoDB集合扫描比例过高"

总结

MongoDB索引优化是提升查询性能的核心手段,关键要点:

  1. 遵循ESR原则:等值→排序→范围,合理设计复合索引
  2. 定期审查索引:使用$indexStats识别无用索引并删除
  3. 善用explain():分析查询计划,确保使用正确的索引
  4. 控制索引数量:索引越多写入越慢,3-5个索引为佳
  5. 利用高级特性:部分索引、稀疏索引、隐藏索引减少开销

注:本文基于MongoDB 7.0和Debian 12编写,索引优化策略需根据实际业务场景调整。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注