Appearance
索引
索引是提高MongoDB查询性能的关键。本章将介绍MongoDB索引的类型、创建方法、管理操作以及查询优化技巧。
索引概述
什么是索引
javascript
// 索引是一种数据结构,用于快速查找文档
// MongoDB索引的特点:
// 1. 类似于书的目录,可以快速定位数据
// 2. 索引存储在内存中,查询时优先使用索引
// 3. 索引会占用存储空间
// 4. 索引会影响写入性能(需要维护索引)
// 默认索引
// MongoDB在每个集合上默认创建_id字段的唯一索引
// 查看集合的索引
db.users.getIndexes()
// 输出示例:
// [
// {
// v: 2,
// key: { _id: 1 },
// name: '_id_'
// }
// ]索引类型
javascript
// MongoDB支持的索引类型:
// 1. 单字段索引(Single Field Index)
// 对单个字段建立索引
// 2. 复合索引(Compound Index)
// 对多个字段建立索引
// 3. 多键索引(Multikey Index)
// 对数组字段建立索引
// 4. 文本索引(Text Index)
// 用于全文搜索
// 5. 地理空间索引(Geospatial Index)
// 用于地理位置查询
// 6. 哈希索引(Hashed Index)
// 对字段值进行哈希
// 7. 通配符索引(Wildcard Index)
// 对未知字段建立索引创建索引
单字段索引
javascript
// 创建单字段索引
// 基本语法
db.collection.createIndex({ field: 1 }) // 1表示升序,-1表示降序
// 创建name字段的升序索引
db.users.createIndex({ name: 1 })
// 输出:'name_1'(索引名称)
// 创建降序索引
db.users.createIndex({ age: -1 })
// 指定索引名称
db.users.createIndex(
{ email: 1 },
{ name: "email_index" }
)
// 创建唯一索引
db.users.createIndex(
{ email: 1 },
{ unique: true }
)
// 创建稀疏索引(只索引存在该字段的文档)
db.users.createIndex(
{ nickname: 1 },
{ sparse: true }
)
// 创建部分索引(只索引满足条件的文档)
db.users.createIndex(
{ status: 1 },
{
partialFilterExpression: {
age: { $gte: 18 } // 只索引age>=18的文档
}
}
)
// 后台创建索引(不阻塞其他操作)
db.users.createIndex(
{ name: 1 },
{ background: true }
)复合索引
javascript
// 创建复合索引(多个字段)
// 创建复合索引
db.users.createIndex({ city: 1, age: -1 })
// 复合索引的使用规则:
// 1. 最左前缀原则:查询条件必须包含索引的第一个字段
// 2. 索引顺序影响查询效率
// 可以使用该索引的查询:
db.users.find({ city: "北京" }) // 使用索引
db.users.find({ city: "北京", age: { $gte: 20 } }) // 使用索引
db.users.find({ city: "北京" }).sort({ age: -1 }) // 使用索引
// 不能使用该索引的查询:
db.users.find({ age: { $gte: 20 } }) // 不包含city字段
// 创建多字段复合索引
db.orders.createIndex({
userId: 1,
status: 1,
createdAt: -1
})
// 指定索引过期时间(TTL索引)
db.logs.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 } // 文档在创建后3600秒自动删除
)多键索引
javascript
// 多键索引(数组字段索引)
// 对数组字段创建索引
db.products.createIndex({ tags: 1 })
// 如果字段值是数组,MongoDB自动创建多键索引
// 对数组中的嵌套字段创建索引
db.products.createIndex({ "variants.price": 1 })
// 多键索引的使用
db.products.find({ tags: "电子" }) // 使用多键索引
// 注意:复合索引中只能有一个多键索引字段文本索引
javascript
// 文本索引用于全文搜索
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 创建全字段文本索引
db.articles.createIndex({ "$**": "text" })
// 指定权重
db.articles.createIndex(
{
title: "text",
content: "text"
},
{
weights: {
title: 10, // title权重更高
content: 5
},
default_language: "none" // 不使用语言分析器
}
)
// 使用文本索引搜索
db.articles.find({ $text: { $search: "MongoDB 教程" } })
// 搜索包含任意一个词
db.articles.find({ $text: { $search: "MongoDB 教程" } })
// 搜索必须包含某个词(使用双引号)
db.articles.find({ $text: { $search: "\"MongoDB\" 教程" } })
// 排除某个词(使用减号)
db.articles.find({ $text: { $search: "MongoDB -MySQL" } })
// 计算相关性得分
db.articles.find(
{ $text: { $search: "MongoDB" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
// 注意:每个集合只能有一个文本索引地理空间索引
javascript
// 地理空间索引
// 2dsphere索引(地球表面坐标)
db.places.createIndex({ location: "2dsphere" })
// 2d索引(平面坐标)
db.places.createIndex({ location: "2d" })
// 插入地理位置数据
db.places.insertOne({
name: "北京天安门",
location: {
type: "Point",
coordinates: [116.397, 39.909] // [经度, 纬度]
}
})
// 附近查询
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.397, 39.909]
},
$maxDistance: 1000 // 最大距离(米)
}
}
})
// 范围查询
db.places.find({
location: {
$geoWithin: {
$centerSphere: [
[116.397, 39.909], // 圆心
10 / 6378.1 // 半径(弧度)
]
}
}
})哈希索引
javascript
// 哈希索引
// 创建哈希索引
db.users.createIndex({ userId: "hashed" })
// 哈希索引特点:
// 1. 支持等值查询,不支持范围查询
// 2. 分布均匀,适合分片键
// 3. 不能用于排序
// 使用哈希索引查询
db.users.find({ userId: 12345 }) // 等值查询可以使用
// 不能使用哈希索引的查询
db.users.find({ userId: { $gt: 10000 } }) // 范围查询不能使用管理索引
查看索引
javascript
// 查看集合的所有索引
db.users.getIndexes()
// 查看索引大小
db.users.totalIndexSize()
// 查看索引详细信息
db.users.aggregate([
{ $indexStats: {} }
])
// 查看特定索引的使用情况
db.users.aggregate([
{ $indexStats: {} },
{ $match: { name: "name_1" } }
])删除索引
javascript
// 删除索引
// 按名称删除索引
db.users.dropIndex("name_1")
// 按规范删除索引
db.users.dropIndex({ name: 1 })
// 删除所有索引(除了_id索引)
db.users.dropIndexes()
// 删除多个指定索引
db.users.dropIndexes(["name_1", "age_-1"])修改索引
javascript
// MongoDB不支持直接修改索引
// 需要先删除再重新创建
// 修改索引步骤:
// 1. 删除旧索引
db.users.dropIndex("old_index")
// 2. 创建新索引
db.users.createIndex({ name: 1 }, { name: "new_index" })
// 隐藏索引(MongoDB 4.4+)
// 隐藏索引不会被查询使用,但会继续维护
db.users.hideIndex("name_1")
// 取消隐藏
db.users.unhideIndex("name_1")执行计划分析
explain方法
javascript
// 使用explain分析查询执行计划
// 基本用法
db.users.find({ name: "张三" }).explain()
// 详细模式
db.users.find({ name: "张三" }).explain("executionStats")
// 全部信息
db.users.find({ name: "张三" }).explain("allPlansExecution")
// 输出关键字段说明:
// - winningPlan: 被选中的执行计划
// - stage: 执行阶段(COLLSCAN全表扫描,IXSCAN索引扫描)
// - indexName: 使用的索引名称
// - executionTimeMillis: 执行时间(毫秒)
// - totalDocsExamined: 扫描的文档数
// - nReturned: 返回的文档数
// 判断是否使用索引
var plan = db.users.find({ name: "张三" }).explain()
if (plan.queryPlanner.winningPlan.stage === "COLLSCAN") {
print("全表扫描,未使用索引")
} else if (plan.queryPlanner.winningPlan.stage === "IXSCAN") {
print("使用了索引: " + plan.queryPlanner.winningPlan.indexName)
}优化查询
javascript
// 查询优化技巧
// 1. 使用hint强制使用指定索引
db.users.find({ name: "张三" }).hint({ name: 1 })
// 2. 检查扫描文档数与返回文档数的比例
var stats = db.users.find({ name: "张三" }).explain("executionStats")
var examined = stats.executionStats.totalDocsExamined
var returned = stats.executionStats.nReturned
print("扫描文档数: " + examined)
print("返回文档数: " + returned)
print("比例: " + (examined / returned))
// 理想情况是1:1,比例过大说明需要优化
// 3. 使用覆盖索引
// 覆盖索引:查询的所有字段都在索引中,不需要回表
db.users.createIndex({ name: 1, email: 1 })
db.users.find(
{ name: "张三" },
{ _id: 0, name: 1, email: 1 } // 只返回索引字段
).explain("executionStats")
// 查看executionStats.totalDocsExamined是否为0
// 4. 避免使用$where和$regex开头通配符
// 这些操作无法使用索引
// 5. 使用projection减少返回数据量
db.users.find({ name: "张三" }, { name: 1, email: 1, _id: 0 })索引设计原则
javascript
// 索引设计原则
// 1. ESR原则(Equality-Sort-Range)
// 等值查询字段 -> 排序字段 -> 范围查询字段
// 示例:查询城市为北京的用户,按年龄排序,筛选薪资范围
db.users.find({ city: "北京", salary: { $gte: 5000 } }).sort({ age: 1 })
// 推荐索引:
db.users.createIndex({ city: 1, age: 1, salary: 1 })
// city: 等值查询(Equality)
// age: 排序字段(Sort)
// salary: 范围查询(Range)
// 2. 选择性高的字段优先
// 选择性 = 不同值数量 / 总文档数
// 选择性越高,索引效果越好
// 3. 避免过多索引
// - 索引占用存储空间
// - 影响写入性能
// - 建议单集合索引不超过5-10个
// 4. 使用复合索引代替多个单字段索引
// 5. 定期检查索引使用情况
db.users.aggregate([
{ $indexStats: {} },
{ $match: { accesses: { $eq: 0 } } } // 查找未使用的索引
])本章小结
本章介绍了MongoDB索引的相关知识:
- 索引概述:理解索引的概念和类型
- 创建索引:掌握单字段、复合、多键、文本、地理空间、哈希索引的创建
- 管理索引:学会查看、删除、修改索引
- 执行计划:学会使用explain分析查询性能
- 查询优化:掌握覆盖索引、hint等优化技巧
- 设计原则:了解ESR原则和索引设计最佳实践
下一章,我们将学习聚合框架,了解MongoDB强大的数据处理能力。
