写在前面:索引是数据库查询性能的关键,MongoDB提供了丰富的索引类型来满足不同场景的需求。本篇将详细介绍MongoDB索引的创建、使用、管理和优化技巧,帮助您打造高效的MongoDB查询。
文章目录
-
- 一、索引基础概念
-
- [1.1 什么是索引?](#1.1 什么是索引?)
- [1.2 索引结构](#1.2 索引结构)
- [1.3 索引类型](#1.3 索引类型)
- 二、单字段索引
-
- [2.1 创建单字段索引](#2.1 创建单字段索引)
- [2.2 查看索引](#2.2 查看索引)
- [2.3 删除索引](#2.3 删除索引)
- 三、复合索引
-
- [3.1 创建复合索引](#3.1 创建复合索引)
- [3.2 复合索引原理](#3.2 复合索引原理)
- [3.3 最左前缀原则](#3.3 最左前缀原则)
- [3.4 索引顺序选择](#3.4 索引顺序选择)
- 四、多键索引
-
- [4.1 数组字段索引](#4.1 数组字段索引)
- [4.2 嵌套数组索引](#4.2 嵌套数组索引)
- 五、文本索引
-
- [5.1 创建文本索引](#5.1 创建文本索引)
- [5.2 文本搜索](#5.2 文本搜索)
- [5.3 文本搜索排序](#5.3 文本搜索排序)
- 六、唯一索引
-
- [6.1 创建唯一索引](#6.1 创建唯一索引)
- [6.2 唯一索引与稀疏索引](#6.2 唯一索引与稀疏索引)
- 七、地理空间索引
-
- [7.1 创建地理空间索引](#7.1 创建地理空间索引)
- [7.2 地理位置查询](#7.2 地理位置查询)
- 八、索引管理
-
- [8.1 查看索引统计](#8.1 查看索引统计)
- [8.2 重建索引](#8.2 重建索引)
- [8.3 索引属性](#8.3 索引属性)
- 九、查询分析与优化
-
- [9.1 使用 explain](#9.1 使用 explain)
- [9.2 性能指标解读](#9.2 性能指标解读)
- [9.3 慢查询日志](#9.3 慢查询日志)
- 十、实战:百万级数据优化
-
- [10.1 创建测试数据](#10.1 创建测试数据)
- [10.2 优化前后对比](#10.2 优化前后对比)
- [10.3 索引设计原则](#10.3 索引设计原则)
- 十一、总结
一、索引基础概念
1.1 什么是索引?
📚 索引概念:
索引就像书籍的目录,
可以快速定位到需要的内容,
而不需要逐页翻找。
无索引:全表扫描 → 慢
有索引:直接定位 → 快
1.2 索引结构
📊 B-Tree索引结构:
[中间节点]
/ | \
[叶子节点1] [叶子节点2] [叶子节点3]
↓ ↓ ↓
数据页 数据页 数据页
特点:
- 平衡树结构,查询复杂度 O(log n)
- 叶子节点按顺序存储
- 支持范围查询和排序
1.3 索引类型
🗂️ MongoDB索引类型:
1. 单字段索引 - 最常用
2. 复合索引 - 多字段组合
3. 多键索引 - 数组字段
4. 文本索引 - 全文搜索
5. 哈希索引 - 等值查询
6. 地理空间索引 - 位置查询
7. 唯一索引 - 约束
8. 稀疏索引 - 稀疏字段
二、单字段索引
2.1 创建单字段索引
javascript
// 为 age 字段创建索引
db.users.createIndex({ age: 1 })
// 1 表示升序,-1 表示降序
db.users.createIndex({ age: -1 })
// 为嵌套字段创建索引
db.orders.createIndex({ "customer.address.city": 1 })
// 指定索引名称
db.users.createIndex(
{ age: 1 },
{ name: "idx_user_age" }
)
2.2 查看索引
javascript
// 查看集合的所有索引
db.users.getIndexes()
// 返回结果示例:
[
{
"v": 2,
"key": { "_id": 1 },
"name": "_id_"
},
{
"v": 2,
"key": { "age": 1 },
"name": "age_1"
}
]
2.3 删除索引
javascript
// 根据索引名称删除
db.users.dropIndex("age_1")
// 根据索引键删除
db.users.dropIndex({ age: 1 })
// 删除所有非默认索引
db.users.dropIndexes()
// 注意:不能删除 _id 索引
三、复合索引
3.1 创建复合索引
javascript
// 为 users 集合创建复合索引:先按 city 排序,再按 age 排序
db.users.createIndex({ city: 1, age: -1 })
// 索引顺序:city(升序) + age(降序)
3.2 复合索引原理
🔍 复合索引工作原理:
假设索引:{ city: 1, age: -1 }
数据示例:
{ city: "北京", age: 25 }
{ city: "北京", age: 30 }
{ city: "上海", age: 20 }
{ city: "上海", age: 28 }
{ city: "深圳", age: 25 }
索引存储顺序:
1. 北京-30
2. 北京-25
3. 深圳-25
4. 上海-28
5. 上海-20
3.3 最左前缀原则
💡 最左前缀原则:
复合索引 { city: 1, age: -1 } 可以支持:
✅ { city: "北京" } - 完全使用
✅ { city: "北京", age: 25 } - 完全使用
✅ { city: "北京", age: { $gt: 20 } } - 完全使用
❌ { age: 25 } - 无法使用
❌ { age: { $gt: 20 } } - 无法使用
⚠️ { age: { $gt: 20 }, city: "北京" } - 可以使用(MongoDB会自动优化)
3.4 索引顺序选择
javascript
// 场景:经常查询某个城市的用户,按年龄排序
// 方案1:city在前
db.users.createIndex({ city: 1, age: -1 })
// 查询:
db.users.find({ city: "北京" }).sort({ age: -1 }) // ✅ 使用索引
db.users.find({ city: "北京" }) // ✅ 使用索引
db.users.find({ age: { $gt: 20 } }) // ❌ 不使用索引
// 方案2:age在前
db.users.createIndex({ age: -1, city: 1 })
// 查询:
db.users.find({ age: { $gt: 20 } }) // ✅ 使用索引
db.users.find({ age: { $gt: 20 } }).sort({ city: 1 }) // ✅ 使用索引
db.users.find({ city: "北京" }) // ❌ 不使用索引
四、多键索引
4.1 数组字段索引
javascript
// 为数组字段创建索引
db.products.createIndex({ tags: 1 })
// 插入测试数据
db.products.insertMany([
{ name: "iPhone", tags: ["手机", "电子产品", "苹果"] },
{ name: "MacBook", tags: ["电脑", "电子产品", "苹果"] },
{ name: "小米手机", tags: ["手机", "电子产品", "国产"] }
])
// 查询使用索引
db.products.find({ tags: "手机" }) // ✅ 使用多键索引
4.2 嵌套数组索引
javascript
// 订单中包含items数组,每个item有product字段
db.orders.insertMany([
{
orderId: "O001",
items: [
{ product: "iPhone", quantity: 1 },
{ product: "AirPods", quantity: 2 }
]
},
{
orderId: "O002",
items: [
{ product: "MacBook", quantity: 1 }
]
}
])
// 为嵌套数组字段创建索引
db.orders.createIndex({ "items.product": 1 })
// 查询
db.orders.find({ "items.product": "iPhone" }) // ✅ 使用索引
五、文本索引
5.1 创建文本索引
javascript
// 为文章标题和内容创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 或者指定权重
db.articles.createIndex(
{ title: "text", content: "text" },
{ weights: { title: 10, content: 1 } }
)
// 单一字段文本索引
db.products.createIndex({ description: "text" })
5.2 文本搜索
javascript
// 插入测试数据
db.articles.insertMany([
{ title: "MongoDB入门教程", content: "学习MongoDB基础" },
{ title: "Redis缓存实战", content: "Redis高性能缓存" },
{ title: "MongoDB索引优化", content: "MongoDB查询优化技巧" }
])
// 文本搜索
db.articles.find({ $text: { $search: "MongoDB" } })
// 搜索多个词(OR)
db.articles.find({ $text: { $search: "MongoDB 教程" } })
// 搜索短语(AND)
db.articles.find({ $text: { $search: "\"MongoDB 入门\"" } })
// 排除某个词
db.articles.find({ $text: { $search: "MongoDB -Redis" } })
5.3 文本搜索排序
javascript
// 按相关性排序
db.articles.find(
{ $text: { $search: "MongoDB" } },
{ score: { $meta: "textScore" } }
).sort(
{ score: { $meta: "textScore" } }
)
// 结果会按相关性得分排序
六、唯一索引
6.1 创建唯一索引
javascript
// 为邮箱创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 复合唯一索引
db.users.createIndex(
{ email: 1, status: 1 },
{ unique: true }
)
// 尝试插入重复邮箱会报错
db.users.insertOne({ email: "test@example.com" })
// Error: E11000 duplicate key error
6.2 唯一索引与稀疏索引
javascript
// 稀疏索引:只索引非空值
db.users.createIndex(
{ phone: 1 },
{ unique: true, sparse: true }
)
// 场景:phone字段不是必须的,但如果有值必须唯一
// sparse: true 表示不索引 null 或不存在的字段
七、地理空间索引
7.1 创建地理空间索引
javascript
// 2dsphere 索引:用于地球表面的坐标
db.stores.createIndex({ location: "2dsphere" })
// 2d 索引:用于平面坐标
db.points.createIndex({ location: "2d" })
7.2 地理位置查询
javascript
// 插入带地理位置的店铺数据
db.stores.insertMany([
{
name: "店铺A",
location: { type: "Point", coordinates: [116.4074, 39.9042] } // 北京
},
{
name: "店铺B",
location: { type: "Point", coordinates: [121.4737, 31.2304] } // 上海
},
{
name: "店铺C",
location: { type: "Point", coordinates: [114.0579, 22.5431] } // 深圳
}
])
// 查询附近1km内的店铺
db.stores.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [116.4074, 39.9042] },
$maxDistance: 1000 // 1000米
}
}
})
八、索引管理
8.1 查看索引统计
javascript
// 查看集合索引信息
db.users.getIndexStats()
// 查看索引大小
db.users.stats().indexSizes
8.2 重建索引
javascript
// 重建集合的所有索引
db.users.reIndex()
// 效果:删除并重新创建所有索引
// 适用于:数据大量删除后,索引文件过大
8.3 索引属性
javascript
// 创建带属性的索引
db.users.createIndex(
{ email: 1 },
{
unique: true, // 唯一索引
sparse: true, // 稀疏索引
expireAfterSeconds: 3600, // TTL索引,3600秒后自动删除
background: true // 后台创建,不阻塞业务
}
)
九、查询分析与优化
9.1 使用 explain
javascript
// 分析查询计划
db.users.find({ age: { $gt: 25 }, city: "北京" })
.explain("executionStats")
// 返回结果关键字段:
{
"queryPlanner": {
"plannerVersion": 1,
"namespace": "myapp.users",
"indexFilterSet": false,
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN", // 使用索引扫描
"indexName": "age_1_city_1",
...
}
}
},
"executionStats": {
"executionTimeMillis": 2, // 执行时间(毫秒)
"totalDocsExamined": 100, // 扫描的文档数
"totalKeysExamined": 50, // 扫描的索引键数
"nReturned": 10 // 返回的文档数
}
}
9.2 性能指标解读
📊 性能分析指标:
✅ 好的查询:
- stage: "IXSCAN" - 使用索引
- totalDocsExamined: 10
- totalKeysExamined: 10
- executionTimeMillis: 1
❌ 差的查询:
- stage: "COLLSCAN" - 全表扫描
- totalDocsExamined: 1000000
- executionTimeMillis: 5000
9.3 慢查询日志
javascript
// 查看慢查询(默认 > 100ms)
db.system.profile.find().pretty()
// 设置慢查询阈值(毫秒)
db.setProfilingLevel(1, 100) // 记录超过100ms的查询
// 查看当前配置
db.getProfilingStatus()
十、实战:百万级数据优化
10.1 创建测试数据
javascript
// 插入100万条测试数据
const bulk = [];
for (let i = 0; i < 1000000; i++) {
bulk.push({
userId: i,
username: "user" + i,
age: Math.floor(Math.random() * 100),
city: ["北京", "上海", "深圳", "广州"][Math.floor(Math.random() * 4)],
status: ["active", "inactive"][Math.floor(Math.random() * 2)],
createdAt: new Date()
});
// 每1000条插入一次
if (bulk.length === 1000) {
db.users.insertMany(bulk);
bulk = [];
}
}
10.2 优化前后对比
javascript
// ❌ 优化前:无索引,查询慢
db.users.find({ city: "北京", age: { $gt: 25 } })
.explain("executionStats")
// 结果:COLLSCAN,扫描100万条,耗时 2000ms
// ✅ 优化后:创建复合索引
db.users.createIndex({ city: 1, age: 1 })
// 再次查询
db.users.find({ city: "北京", age: { $gt: 25 } })
.explain("executionStats")
// 结果:IXSCAN,扫描1000条,耗时 5ms
10.3 索引设计原则
📋 索引设计最佳实践:
1. 优先为 WHERE 子句中的字段创建索引
2. 考虑查询的选择性
- 选择性高的字段放前面
- 尽量使用唯一索引
3. 避免创建过多索引
- 每个索引都会占用空间
- 写入时需要维护所有索引
4. 使用复合索引替代多个单字段索引
- 减少索引数量
- 利用最左前缀原则
5. 定期检查并删除无用索引
- db.users.getIndexes() 查看
- dropIndex() 删除
十一、总结
📊 本篇总结:
✅ 掌握内容:
- 索引基础概念和类型
- 单字段索引创建和使用
- 复合索引与最左前缀原则
- 多键索引(数组字段)
- 文本索引(全文搜索)
- 唯一索引与稀疏索引
- 地理空间索引
- 索引管理与属性
- 查询分析与优化
- 百万级数据优化实战
作者 :刘~浪地球
更新时间 :2026-05-07
本文声明:原创不易,转载需授权!