MongoDB 高级索引(Advanced Indexing)完全指南
核心目标:用对索引,性能翻 100 倍 —— 覆盖 复合、部分、稀疏、TTL、文本、地理、多键、哈希、分片键 等全场景。
一、索引类型总览
| 类型 | 用途 | 关键选项 |
|---|---|---|
| 单一索引 | 单字段 | createIndex({ field: 1 }) |
| 复合索引 | 多字段 | createIndex({ a: 1, b: -1 }) |
| 部分索引(Partial) | 条件索引 | partialFilterExpression |
| 稀疏索引(Sparse) | 跳过缺失字段 | sparse: true |
| TTL 索引 | 自动过期 | expireAfterSeconds |
| 文本索引(Text) | 全文搜索 | weights, default_language |
| 地理索引(2d/2dsphere) | 位置查询 | 2dsphere |
| 哈希索引(Hashed) | 分片键均匀分布 | hashed |
| 多键索引(Multikey) | 数组字段 | 自动创建 |
| 通配符索引(Wildcard) | 动态字段 | $** |
二、复合索引:黄金法则 ESR
Equality → Sort → Range
// 查询:status="active" + 按 score 降序 + age > 18
db.users.createIndex({
status: 1, // 等值
score: -1, // 排序
age: 1 // 范围
})
能支持的查询:
{ status: "active" }{ status: "active", score: { $gte: 90 } }{ status: "active", age: { $gt: 18 } }{ status: "active", score: -1, age: { $gt: 18 } }
三、部分索引(Partial Index)—— 节省 90% 空间
// 只为 status="active" 的文档建索引
db.users.createIndex(
{ email: 1 },
{
partialFilterExpression: { status: "active" }
}
)
适用:高频查询字段 + 过滤条件固定
四、稀疏索引(Sparse)—— 避免 null 膨胀
// 只为存在 phone 的文档建索引
db.users.createIndex(
{ phone: 1 },
{ sparse: true }
)
vs 部分索引:
sparse: 字段存在即可partial: 可加复杂条件
五、TTL 索引:自动清理日志
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 } // 1 小时后删除
)
注意:字段必须是
Date,每 60 秒检查一次
六、文本索引:全文搜索
// 创建文本索引
db.articles.createIndex({
title: "text",
content: "text",
tags: "text"
}, {
weights: { title: 10, content: 5, tags: 1 },
default_language: "chinese"
})
// 搜索
db.articles.find({
$text: { $search: "MongoDB 性能" }
})
排序:$meta: "textScore"
db.articles.find(
{ $text: { $search: "索引" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
七、地理索引:附近的人
// 2dsphere(地球)
db.places.createIndex({ location: "2dsphere" })
// 插入 GeoJSON
db.places.insertOne({
name: "咖啡馆",
location: {
type: "Point",
coordinates: [116.39, 39.9] // [经度, 纬度]
}
})
// 附近 5km
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.40, 39.91]
},
$maxDistance: 5000 // 米
}
}
})
八、哈希索引:分片键均匀分布
// 分片键使用 hashed
sh.shardCollection("mydb.logs", { _id: "hashed" })
避免热点写入(如自增
_id)
九、多键索引(Multikey):数组字段
// 自动为数组建多键索引
db.users.createIndex({ tags: 1 })
// 查询
db.users.find({ tags: "mongodb" })
限制:
- 不能与
hashed共用 - 分片键不能是数组
十、通配符索引(Wildcard):动态字段
// 索引 user.profile 下的所有字段
db.users.createIndex({
"profile.$**": 1
})
// 支持
db.users.find({ "profile.city": "Beijing" })
十一、索引管理命令
| 命令 | 用途 |
|---|---|
getIndexes() | 查看索引 |
dropIndex("name") | 删除索引 |
createIndex(..., { background: true }) | 后台建索引 |
collMod | 修改索引选项 |
reIndex() | 重建索引 |
// 查看索引大小
db.users.stats().indexSize
// 后台建索引(不阻塞)
db.users.createIndex({ email: 1 }, { background: true })
十二、生产级索引策略
| 场景 | 推荐索引 |
|---|---|
| 用户登录 | { email: 1 } + unique |
| 文章列表 | { status: 1, createdAt: -1, _id: 1 } |
| 日志查询 | { level: 1, timestamp: -1 } + partial: { level: "error" } |
| 分片键 | { _id: "hashed" } 或 { user_id: 1, date: 1 } |
| 全文搜索 | { title: "text", content: "text" } |
十三、索引优化 Checklist
| 检查项 | 命令 |
|---|---|
| 是否重复索引? | getIndexes() |
| 索引是否被使用? | explain().winningPlan.indexName |
| 索引大小? | stats().indexSize |
| 是否覆盖查询? | PROJECTION_COVERED |
| 是否后台建? | { background: true } |
十四、终极案例:高并发日志系统
// 1. 复合索引(ESR)
db.logs.createIndex({
app: 1, // 等值
level: 1, // 等值
timestamp: -1, // 排序
message: 1 // 投影(覆盖)
})
// 2. 部分索引(只存 error)
db.logs.createIndex(
{ request_id: 1 },
{ partialFilterExpression: { level: "error" } }
)
// 3. TTL 清理旧日志
db.logs.createIndex(
{ timestamp: 1 },
{ expireAfterSeconds: 30 * 24 * 3600 } // 30 天
)
// 4. 查询(覆盖)
db.logs.find(
{ app: "api", level: "error" },
{ message: 1, timestamp: 1, _id: 0 }
)
.sort({ timestamp: -1 })
.limit(100)
.explain()
{
"totalDocsExamined": 0,
"stage": "PROJECTION_COVERED"
}
总结:高级索引黄金三问
| 问题 | 答案 |
|---|---|
| 1. 索引顺序对吗? | 遵循 ESR |
| 2. 索引是否必要? | 用 explain() 验证 |
| 3. 索引是否高效? | 覆盖查询 + 部分索引 |
你现在可以:
// 一行创建生产级索引
db.collection.createIndex(
{ status: 1, createdAt: -1, name: 1 },
{ partialFilterExpression: { status: "active" } }
)
把你的查询贴给我,我 30 秒出最优索引:
- 查询语句(
find/aggregate) - 过滤条件
- 返回字段 + 排序
回复 3 行代码,我生成:索引 + explain + 性能对比!