03. MongoDB 索引类型与查询优化

MongoDB 索引全解: 单键、复合、多键、文本、TTL、稀疏与部分索引,Explain 分析与查询优化

索引是数据库性能的核心。MongoDB 使用 B-Tree 索引,与关系型数据库类似,但有一些独特之处——如多键索引(数组索引)、文本索引、TTL 索引等。理解索引原理并正确使用 Explain 进行分析,是 MongoDB 调优的关键。

1. 索引基础

1.1 创建与管理

// 单键索引
db.users.createIndex({ email: 1 });  // 1 = 升序, -1 = 降序

// 复合索引(最左前缀原则)
db.orders.createIndex({ status: 1, createdAt: -1 });

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

// 查看索引
db.users.getIndexes();
db.users.totalIndexSize();  // 索引占用空间

// 删除索引
db.users.dropIndex("email_1");
db.users.dropIndexes();

1.2 索引对性能的影响

操作无索引有索引
查询集合扫描 COLSCAN索引扫描 IXSCAN
写入慢(需更新索引)
更新可能快(通过索引定位)
内存无额外占用常驻内存

2. 索引类型

2.1 单键索引

// 最基础的索引
db.users.createIndex({ age: 1 });

// 适用查询:
db.users.find({ age: 30 });
db.users.find({ age: { $gte: 18, $lte: 60 } }).sort({ age: 1 });

2.2 复合索引

// 查询模式: WHERE status = ? AND createdAt > ? ORDER BY createdAt DESC
// 索引设计: { status: 1, createdAt: -1 }
db.orders.createIndex({ status: 1, createdAt: -1 });

// ✅ 有效使用:
db.orders.find({ status: "pending" });
db.orders.find({ status: "paid", createdAt: { $gt: yesterday } });
db.orders.find({ status: "pending" }).sort({ createdAt: -1 });

// ❌ 无法使用(缺少 status):
db.orders.find({ createdAt: { $gt: yesterday } });
// ❌ 无法排序(排序方向不符):
db.orders.find({ status: "pending" }).sort({ createdAt: 1 });

最左前缀原则:复合索引 {a, b, c} 可被以下查询使用:

  • {a}, {a, b}, {a, b, c}
  • {a} 加按 b 排序

2.3 多键索引(数组索引)

// 自动为数组字段创建多键索引
db.products.createIndex({ tags: 1 });

// 查询:
db.products.find({ tags: "electronics" });  // IXSCAN
db.products.find({ tags: { $all: ["electronics", "sale"] } });

// 限制: 复合索引中最多一个多键字段
db.orders.createIndex({ items: 1, status: 1 });  // ❌ 可能失败

2.4 文本索引

// 全文搜索索引
db.articles.createIndex({
    title: "text",
    content: "text",
    tags: "text"
}, {
    weights: {
        title: 10,      // 标题匹配权重更高
        content: 5,
        tags: 3
    },
    default_language: "chinese"
});

// 全文查询
db.articles.find({ $text: { $search: "MongoDB indexing" } },
                 { score: { $meta: "textScore" } })
          .sort({ score: { $meta: "textScore" } });

// 短语搜索
db.articles.find({ $text: { $search: "\"exact phrase\"" } });

2.5 TTL 索引

// 自动删除过期文档(适用于验证码、临时数据)
db.sessions.createIndex(
    { createdAt: 1 },
    { expireAfterSeconds: 3600 }  // 1小时过期
);

// TTL 后台线程每 60 秒运行一次,过期时间可能有延迟

2.6 部分索引与稀疏索引

// 稀疏索引: 只对存在该字段的文档建立索引
db.users.createIndex({ phone: 1 }, { sparse: true });

// 部分索引: 只对满足条件的文档建立索引(更有用)
db.orders.createIndex(
    { orderId: 1 },
    { partialFilterExpression: { status: { $eq: "pending" } } }
);
// 只为 pending 订单建立索引,大幅减少索引大小

2.7 其他索引类型

索引类型用途示例
Hashed分片键哈希db.users.createIndex({ _id: "hashed" })
Wildcard动态字段索引db.logs.createIndex({ "$**": 1 })
Clustered按 _id 聚簇存储仅在 MongoDB 5.0+ 时间序列集合支持

3. Explain 分析

3.1 执行计划关键字段

db.orders.find({ status: "pending" }).explain("executionStats");
{
  "queryPlanner": {
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "IXSCAN",
        "indexName": "status_1_createdAt_-1",
        "direction": "forward"
      }
    }
  },
  "executionStats": {
    "nReturned": 150,        // 返回文档数
    "totalDocsExamined": 150, // 扫描文档数
    "totalKeysExamined": 150, // 扫描索引键数
    "executionTimeMillis": 2,  // 执行时间
    "executionStages": {
      "stage": "IXSCAN",
      "works": 151,
      "advanced": 150,
      "isEOF": 1
    }
  }
}

3.2 常见执行阶段

Stage含义是否可接受
COLLSCAN集合扫描大数据量时 ❌
IXSCAN索引扫描
FETCH取完整文档需回表
PROJECTION_COVERED覆盖索引查询✅ 最优
SORT内存排序大数据量时 ❌
LIMIT限制返回数

3.3 索引覆盖查询

// 索引 { _id: 1, status: 1 }
db.orders.find({ _id: 1 }, { status: 1, _id: 1 });
// Covered Query: 只从索引获取数据,不回表

// 执行计划中: "executionStats.totalDocsExamined" = 0

4. 查询优化技巧

技巧说明
使用投影减少回表find({}, { needed: 1 })
利用覆盖索引查询字段都在索引中
$in 数量控制单数组合并为一个 $in,但不超过 1000 个
避免 skip 深分页使用最后文档的 _id 作为游标
排序使用索引复合索引包含排序字段
hint 强制索引确认优化器选择错误时使用

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「mongodb」更多文章

  1. 11. MongoDB 安全认证与备份恢复
  2. 10. MongoDB 性能调优与运维监控
  3. 09. Spring Data MongoDB 实战