MongoDB 索引
为什么需要索引
无索引时 MongoDB 必须执行全集合扫描(COLLSCAN),逐一检查每个文档。索引通过 B-tree 数据结构存储字段的排序子集,大幅加速查询。
⚠️ 索引加速读取,但会增加写入开销。每次写入都需同步更新索引。
索引类型
| 类型 | 用途 | 示例 |
|---|---|---|
| 单字段 | 单字段查询 | { age: 1 } |
| 复合 | 多字段查询 | { userId: 1, createdAt: -1 } |
| 多键 | 数组字段 | { tags: 1 }(自动识别) |
| 文本 | 全文搜索 | { description: "text" } |
| 地理空间 | 位置查询 | { location: "2dsphere" } |
| 哈希 | 分片 / 等值查询 | { userId: "hashed" } |
| TTL | 自动过期 | { expireAt: 1 } + expireAfterSeconds |
| 通配符 | 动态 Schema | { "$**": 1 } |
创建索引
单字段索引
js
// 1 = 升序
db.users.createIndex({ age: 1 })
// -1 = 降序(对单字段索引无实质影响)
db.users.createIndex({ createdAt: -1 })复合索引
js
db.orders.createIndex({ userId: 1, createdAt: -1 })复合索引遵循最左前缀匹配:
{ userId: 1, createdAt: -1 }可覆盖{ userId }查询,但无法覆盖{ createdAt }查询。
ESR 规则 — 复合索引字段顺序
Equality → Sort → Range
= 排序 范围js
// 查询:category = "electronics" AND price > 50,按 name 排序
db.products.find({ category: "electronics", price: { $gt: 50 } })
.sort({ name: 1 })
// 最优索引
db.products.createIndex({ category: 1, name: 1, price: 1 })
// = 排序 范围唯一索引
js
db.users.createIndex({ email: 1 }, { unique: true })TTL 索引
js
// 文档在 expireAt 时间后自动删除
db.sessions.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 })
// 30 分钟后自动删除
db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 1800 })文本索引
js
// 创建(一个集合最多一个文本索引)
db.articles.createIndex({ title: "text", body: "text" })
// 搜索
db.articles.find({ $text: { $search: "MongoDB performance" } })地理空间索引
js
db.places.createIndex({ location: "2dsphere" })
// 附近搜索
db.places.find({
location: {
$nearSphere: {
$geometry: { type: "Point", coordinates: [-73.9667, 40.78] },
$maxDistance: 5000 // 米
}
}
})坐标格式为
[longitude, latitude](经度在前)。
部分索引(Partial Index)
js
// 仅为满足条件的文档建索引,节省空间
db.orders.createIndex(
{ userId: 1 },
{ partialFilterExpression: { status: "active" } }
)分析索引性能
explain()
js
db.users.find({ age: { $gte: 25 } }).explain("executionStats")关注指标:
| 指标 | 含义 |
|---|---|
IXSCAN | 已使用索引 ✅ |
COLLSCAN | 全表扫描,缺少索引 ❌ |
totalDocsExamined | 扫描文档数,应接近 nReturned |
totalDocsExamined: 0 | 覆盖查询(最佳) |
覆盖查询(Covered Query)
当索引包含查询和投影所需的全部字段时,MongoDB 只读索引不读文档:
js
// 索引覆盖所有字段
db.users.createIndex({ name: 1, age: 1 })
db.users.find(
{ name: "Alice" },
{ name: 1, age: 1, _id: 0 }
).explain("executionStats")
// totalDocsExamined: 0 ← 覆盖查询!查看索引使用情况
js
// 索引统计
db.users.aggregate([{ $indexStats: {} }])
// 列出所有索引
db.users.getIndexes()
// 删除索引
db.users.dropIndex("age_1")