索引优化
索引是提升查询性能的关键。MongoDB 支持多种类型的索引,本章将详细介绍索引的类型、创建和使用。
索引基础
什么是索引?
索引是一种数据结构,能够快速定位数据,类似于书籍的目录:
无索引:全表扫描
[文档1] → [文档2] → [文档3] → ... → [文档N]
↓ 逐个比对
找到目标
有索引:快速查找
索引 → [指向文档的指针] → 目标文档
索引类型
| 索引类型 | 说明 | 使用场景 |
|---|---|---|
| 单字段索引 | 索引单个字段 | 常见查询条件 |
| 复合索引 | 索引多个字段 | 多条件查询 |
| 多键索引 | 索引数组字段 | 数组查询 |
| 文本索引 | 全文搜索 | 文本搜索 |
| 地理空间索引 | 地理位置查询 | 附近地点搜索 |
| 哈希索引 | 哈希值索引 | 分片键 |
| 唯一索引 | 唯一值约束 | 防止重复 |
创建索引
createIndex 方法
// 基本语法
db.collection.createIndex(<keys>, <options>)
// 简化写法(升序)
db.users.createIndex({ age: 1 })
// 详细写法
db.users.createIndex({ age: 1 }, { name: "age_index" })
单字段索引
// 为 age 字段创建索引
db.users.createIndex({ age: 1 }) // 1 升序,-1 降序
// 为嵌套字段创建索引(点符号)
db.orders.createIndex({ "customer.city": 1 })
// 为数组字段创建索引(自动成为多键索引)
db.products.createIndex({ tags: 1 })
复合索引
复合索引包含多个字段,字段顺序很重要:
// 创建复合索引
db.orders.createIndex({ status: 1, createdAt: -1 })
// 复合索引支持的查询模式
// 1. 使用索引前缀字段
db.orders.find({ status: "completed" })
// 2. 使用所有字段(顺序无关,可使用索引)
db.orders.find({ status: "completed", createdAt: { $gt: new Date() } })
// 3. 只使用部分字段(跳过前缀,索引效率低)
db.orders.find({ createdAt: { $gt: new Date() } })
复合索引字段顺序选择原则:
- 字段基数(唯一值数量)高的放前面
- 等值查询频繁的字段放前面
- 范围查询放后面
多键索引
为数组字段创建索引时会自动创建多键索引:
// tags 是数组字段
db.products.createIndex({ tags: 1 })
// 数组中任意元素匹配都可以使用索引
db.products.find({ tags: "electronics" })
唯一索引
// 创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 复合唯一索引
db.orders.createIndex({ customerId: 1, orderId: 1 }, { unique: true })
文本索引
用于全文搜索:
// 创建文本索引(可指定权重和语言)
db.articles.createIndex(
{ title: "text", content: "text" },
{ weights: { title: 10, content: 1 }, default_language: "chinese" }
)
// 搜索
db.articles.find({ $text: { $search: "MongoDB 教程" } })
// 文本搜索评分
db.articles.find(
{ $text: { $search: "MongoDB" } },
{ score: { $meta: "textScore" } }
)
地理空间索引
// 2dsphere 索引(地球表面)
db.places.createIndex({ location: "2dsphere" })
// 2d 索引(平面)
db.places.createIndex({ location: "2d" })
哈希索引
主要用于分片:
db.users.createIndex({ _id: "hashed" })
索引管理
查看索引
// 查看集合的所有索引
db.users.getIndexes()
// 查看索引详情
db.users.getIndexSpecs()
删除索引
// 按名称删除
db.users.dropIndex("age_1")
// 按索引键删除
db.users.dropIndex({ age: 1 })
// 删除所有索引(保留 _id 索引)
db.users.dropIndexes()
查看索引使用情况
// 查看查询是否使用了索引
db.users.find({ age: 25 }).explain()
// explain 输出的关键字段
// winningPlan.inputStage.indexName - 使用的索引
// winningPlan.inputStage.indexBounds - 索引范围
索引属性
// 稀疏索引(只索引非空值)
db.users.createIndex({ email: 1 }, { sparse: true })
// 生存时间索引(TTL,自动过期删除)
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 } // 1小时后自动删除
)
// 复合索引的顺序
db.orders.createIndex(
{ status: 1, createdAt: -1 },
{ background: true } // 后台创建
)
索引优化策略
1. 选择合适的索引字段
// 高基数字段(唯一值多)优先
// 好的索引
db.users.createIndex({ email: 1 }) // email 唯一性高
// 不好的索引
db.users.createIndex({ gender: 1 }) // gender 只有几个值
2. 创建复合索引
// 场景:经常查询 "状态=已完成" 且 "创建时间"
db.orders.createIndex({ status: 1, createdAt: -1 })
// 不需要为每个查询创建单独的索引
// 复合索引 { status: 1, createdAt: -1 } 可以支持:
// find({ status: "completed" })
// find({ status: "completed", createdAt: { $gt: ... } })
// find({ status: { $in: [...] }, createdAt: { $gt: ... } })
3. 覆盖查询
索引覆盖是指查询只需要返回索引中的字段,不需要回表查询:
// 创建索引
db.users.createIndex({ age: 1, name: 1 })
// 覆盖查询:只返回索引中的字段
db.users.find(
{ age: 25 },
{ _id: 0, name: 1 } // 只返回 name,不返回 _id
)
// 不覆盖:需要返回未索引的字段
db.users.find({ age: 25 }, { name: 1, email: 1 }) // email 未索引,需要回表
4. 避免索引过多
索引会占用空间并影响写入性能:
// 监控索引大小
db.users.stats().indexSizes
// 评估查询是否使用索引
db.users.find({ age: { $gt: 20 } }).explain("executionStats")
实战示例
1. 电商订单查询优化
// 场景:经常按订单状态和日期查询
db.orders.createIndex({ status: 1, createdAt: -1 })
// 场景:按用户查询订单
db.orders.createIndex({ customerId: 1, createdAt: -1 })
// 场景:按订单号精确查询
db.orders.createIndex({ orderId: 1 }, { unique: true })
2. 用户登录优化
// 场景:按邮箱登录
db.users.createIndex({ email: 1 }, { unique: true })
// 场景:按用户名查询
db.users.createIndex({ username: 1 })
// 复合索引(如果有按用户名+状态的查询)
db.users.createIndex({ username: 1, status: 1 })
3. 日志数据自动清理
// 创建 TTL 索引,7 天后自动删除
db.logs.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 7 * 24 * 60 * 60 }
)
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" } },
{ title: 1, _id: 0, score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
索引最佳实践
- 使用 explain() 分析查询:了解查询是否使用了索引
- 监控慢查询:使用数据库分析器找出需要优化的查询
- 定期维护索引:删除不再使用的索引
- 考虑查询覆盖:减少回表查询
- 平衡读和写:索引能提升读性能,但会影响写性能
// 分析查询计划
db.orders.find({ status: "pending" }).explain("allPlansExecution")
// 查看慢查询阈值(系统默认 100ms)
db.getProfilingLevel()
db.getProfilingStatus()
// 设置慢查询日志
db.setProfilingLevel(1, 100) // 记录超过 100ms 的查询
小结
本章我们学习了:
- 索引基础:索引概念和原理
- 索引类型:单字段、复合、多键、文本、地理空间、哈希索引
- 索引创建:createIndex 方法和选项
- 索引管理:查看、删除、优化
- 索引优化策略:字段选择、复合索引、覆盖查询
- 实战示例:电商、用户、日志、搜索场景的索引设计