一、什么是索引?为什么需要它?
想象一下,你有一本厚厚的字典(相当于 MongoDB 的集合),如果想查 “MongoDB” 这个词,没有目录(索引)的话,你得从第一页翻到最后一页,多费劲!
索引就是数据库的 “目录”,它能让 MongoDB 在查询数据时,不用扫描整个集合,直接定位到目标数据,大大提高查询速度。
没有索引的问题:
当集合数据量很大(比如 100 万条评论),查询时会扫描所有文档(全集合扫描),速度慢得让人崩溃。
有了索引,哪怕数据再多,也能像查字典一样快速找到目标。
- 无索引查询:扫描集合中每个文档(全集合扫描),效率极低
- 有索引查询:直接定位目标数据,查询速度提升10-100倍
- 底层原理:MongoDB使用B-Tree数据结构存储索引(类似MySQL的B+Tree)
当数据量超过10万条时,索引对查询速度的影响会变得非常明显
二、常见的索引类型(小白必学 3 种)
MongoDB 支持多种索引,最常用的是前两种:
1. 单字段索引(最常用)
在单个字段上创建的索引,比如给userid或likenum字段建索引。
# 语法:db.集合名.createIndex({ 字段名: 排序方式 })
# 1表示升序,-1表示降序(单字段索引中排序方式影响不大)
db.comment.createIndex({ "userid": 1 }) # 给userid字段建升序索引
作用:加速对userid字段的查询,比如db.comment.find({ “userid”: “1003” })会快很多。
2. 复合索引(多字段组合)
当查询条件涉及多个字段时,用复合索引。
# 语法:db.集合名.createIndex({ 字段1: 排序方式, 字段2: 排序方式 })
db.comment.createIndex({ "userid": 1, "likenum": -1 })
关键点:字段顺序很重要!
上面的索引会先按userid升序排序,在相同 userid 内,再按likenum降序排序。
适合查询:db.comment.find({ “userid”: “1003”, “likenum”: { $gt: 10 } })
3. 其他特殊索引(了解即可)
- 文本索引:专门用于全文搜索(比如查评论内容包含 “好用” 的文档),如db.articles.createIndex({content: “text”})
- 地理空间索引:用于定位地理位置(比如 “附近的人” 功能),db.places.createIndex({loc: “2dsphere”})。
- 哈希索引:对字段值哈希后建索引,适合分片集群,不支持范围查询,如db.users.createIndex({username: “hashed”})。
三、索引的管理:查看、创建、删除
1. 查看集合中的索引
# 查看comment集合的所有索引
db.comment.getIndexes()
// 结果示例
[
{
"v": 2, // 索引版本
"key": { "_id": 1 }, // 索引字段及排序
"name": "_id_", // 索引名称
"ns": "articledb.comment" // 所属集合
}
]
刚创建的集合会默认有一个_id索引(主键索引),确保_id不重复,不用手动创建。
2. 创建索引(重点)
# 1. 单字段索引:给articleid字段建索引
db.comment.createIndex({ "articleid": 1 })
# 2. 复合索引:给userid和createdatetime建索引
db.comment.createIndex({ "userid": 1, "createdatetime": -1 })
# 3. 唯一索引:确保字段值不重复(比如不允许相同的userid+articleid重复评论)
db.comment.createIndex({ "userid": 1, "articleid": 1 }, { unique: true })
常用参数(options):
- unique: true:创建唯一索引,避免重复数据(如上面的例子)。
- background: true:后台创建索引,不阻塞其他操作(数据量大时推荐)。
3. 删除索引
# 方法1:通过索引名称删除(索引名称可通过getIndexes()查看)
db.comment.dropIndex("userid_1") # 删除userid的单字段索引
# 方法2:通过索引规则删除
db.comment.dropIndex({ "userid": 1, "likenum": -1 }) # 删除复合索引
四、怎么知道索引生效了?用执行计划!
创建索引后,怎么确认它真的被用到了?用explain()查看执行计划:
# 查看查询是否使用了索引
db.comment.find({ "userid": "1003" }).explain("executionStats")
关注关键输出:
"winningPlan": {
"stage": "FETCH", // 数据获取阶段
"inputStage": {
"stage": "IXSCAN", // 索引扫描
"indexName": "userid_1", // 使用的索引
"direction": "forward" // 扫描方向
}
},
"executionStats": {
"executionTimeMillis": 0, // 执行时间(毫秒)
"totalKeysExamined": 1, // 扫描索引键数
"totalDocsExamined": 1 // 扫描文档数
}
看这两个关键指标:
executionStats.executedScannedDocuments:扫描的文档数。
有索引:这个数字很小(接近查询结果数)。
没索引:这个数字等于集合总文档数(全表扫描)。
五、小技巧:“涵盖的查询” 更高效
如果查询的字段全都是索引字段,MongoDB 会直接从索引中取数据,不用访问原始文档,速度超快!
# 假设已有索引:{ "userid": 1, "nickname": 1 }
# 查询和投影都只用到索引字段
db.comment.find(
{ "userid": "1003" }, # 查询条件
{ "userid": 1, "nickname": 1, "_id": 0 } # 只返回这两个字段
)
总结:索引的 “利与弊”
- 利:大幅提高查询速度,尤其是数据量大的时候。
- 弊:占用额外存储空间,且会降低插入 / 更新 / 删除的速度(因为要维护索引)。
最佳实践:
- 只为常用查询字段建索引。
- 复合索引的字段顺序要符合查询习惯(频繁过滤的字段放前面)。
- 定期用explain()检查索引是否被有效使用。