
目录
-
- 引言
- [第一节:天生的"索引小能手"------`_id` 索引 🆔](#第一节:天生的“索引小能手”——
_id
索引 🆔) - [第二节:自己动手,丰衣足食------创建索引 🔑🛠️](#第二节:自己动手,丰衣足食——创建索引 🔑🛠️)
- 第三节:索引的"类型"------因"材"施"索"!🌳🔑
- 第四节:索引的"体检"------看看我的索引们!👁️🗨️
- 第五节:索引的"移除"------清理不再需要的索引!🧹
- 第六节:索引的"诊断"------你的查询真的用了索引吗?📊🤔
- 第七节:索引策略与最佳实践------让索引发挥最大效用!🏆
- 第八节:总结索引操作!🔑💪
🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】...等
如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning
看之前可以先了解一下MongoDB是什么:【MongoDB篇】万字带你初识MongoDB!
引言
咱们的 MongoDB 探索之旅一路走来,已经认识了仓库(数据库)、文件柜(集合),也掌握了对文件(文档)的 CRUD 操作。现在,是时候给我们的文件柜装上一个"快速检索系统"了!
了解数据库操作请看:【MongoDB篇】MongoDB的数据库操作!
了解集合操作请看:【MongoDB篇】MongoDB的集合操作!
了解文档操作请看:【MongoDB篇】MongoDB的文档操作!
没错,我们要讲解的,是让 MongoDB 查询飞起来的"秘密武器"------索引 (Index)!🔑
想象一下,你的文件柜里装了几百万个包裹📦。如果有人问你:"找出所有名字里包含'架构师'的包裹",如果你没有索引,你得把文件柜里所有的包裹一个一个拿出来,打开,看看名字,再放回去... 这得多慢啊!😩 这就是数据库里的全集合扫描 (Full Collection Scan - COLLSCAN),速度慢、效率低,特别是在数据量大的时候,简直是性能杀手!🐢💥
那索引是干啥的呢?索引就像是:
- 图书馆的书籍目录:你可以快速找到某本书在哪里,而不用翻遍整个图书馆。📚➡️🔍
- 书本后面的索引页:你想找书里某个特定词或概念出现的地方,查索引页能直接告诉你页码。📖➡️📍
在 MongoDB 里,索引是一种特殊的数据结构(通常是 B-tree),它存储了集合中一个或多个字段的值,并且这些值是有序的!每个索引项都指向原始文档在集合中的位置。
当你在一个有索引的字段上进行查询时,MongoDB 不用再扫描整个集合,而是去扫描这个有序的、通常比集合小得多的索引。在索引中找到匹配项后,直接通过索引项指向的"指针"快速定位到原始文档的位置,大大提高了查询速度!⚡️
核心目的: 提高读取 (Read) 操作的性能,特别是查询 (find) 和排序 (sort) 的速度。
第一节:天生的"索引小能手"------_id
索引 🆔
首先,MongoDB 有一个天生自带的、非常特殊的索引:_id
字段上的索引!
在你创建任何集合时,MongoDB 会自动在 _id
字段上创建一个唯一的 (Unique) 、单字段索引 (Single Field Index)。
这意味着:
- 每个文档的
_id
都是唯一的,MongoDB 会强制保证这一点。 - 通过
_id
查找文档速度飞快,因为 MongoDB 可以直接通过索引定位到目标文档!⚡
所以,如果你知道文档的 _id
,通过 db.collection.findOne({ _id: ... })
或 db.collection.find({ _id: { $in: [...] } })
进行查询是最高效的方式!
第二节:自己动手,丰衣足食------创建索引 🔑🛠️
除了自带的 _id
索引,你可以根据你的查询需求,为其他字段或字段组合创建索引。
创建索引使用 db.collection.createIndex(keys, options)
命令。
keys: <document>
:一个文档,指定要在哪些字段上创建索引,以及索引的方向。{ field: 1 }
:在field
字段上创建升序 (Ascending) 索引。{ field: -1 }
:在field
字段上创建降序 (Descending) 索引。- 可以指定多个字段来创建复合索引 (Compound Index) :
{ field1: 1, field2: -1, ... }
。字段的顺序非常重要!🔑➡️➡️➡️
options: <document>
:一个可选的文档,用于指定索引的额外配置。
创建索引示例:
javascript
use mydatabase;
// 在 name 字段上创建升序索引
db.users.createIndex({ name: 1 });
// 在 age 字段上创建降序索引
db.users.createIndex({ age: -1 });
// 在 city 和 age 字段上创建复合索引 (先按 city 升序,再按 age 降序)
db.users.createIndex({ city: 1, age: -1 });
// 在 email 字段上创建唯一索引 (确保 email 不重复)
db.users.createIndex({ email: 1 }, { unique: true });
// 在 status 字段上创建稀疏索引 (只索引存在 status 字段的文档)
db.users.createIndex({ status: 1 }, { sparse: true });
// 在 created_at 字段上创建 TTL 索引,文档在创建后 3600 秒 (1小时) 自动过期删除
db.users.createIndex({ created_at: 1 }, { expireAfterSeconds: 3600 });
// 在 tags 数组字段上创建多键索引 (Multikey Index) - 通常只需指定字段和方向
db.products.createIndex({ tags: 1 });
// 在 content 字段上创建文本索引,用于全文搜索
db.articles.createIndex({ content: "text" });
// 在 location 字段上创建 2dsphere 地理空间索引
db.places.createIndex({ location: "2dsphere" });
// 创建一个带自定义名称的索引
db.users.createIndex({ name: 1, city: 1 }, { name: "name_city_idx" });
常用索引选项详解:
unique: <boolean>
:如果设置为true
,MongoDB 会强制确保索引字段的值在集合中是唯一的。插入或更新时,如果某个文档的该字段值与已有文档重复,操作会失败。可以用作强制数据唯一性约束,比如邮箱、用户名等。🎯sparse: <boolean>
:如果设置为true
,索引只会包含那些文档中存在 被索引字段的条目。对于那些不包含该字段的文档,则不会创建索引条目。这可以节省索引空间,特别是当某个字段在很多文档中是可选或不存在的时候。比如一个spouse_name
字段,只有已婚用户才有,为它创建稀疏索引就很合适。👍name: <string>
:为你的索引指定一个人类可读的名称。如果不指定,MongoDB 会根据索引字段和方向自动生成一个名称(比如name_1
,city_1_age_-1
)。自定义名称可以让你更容易地管理和删除索引!强烈推荐在复合索引或带有特殊选项的索引上使用自定义名称!📝expireAfterSeconds: <number>
:将索引创建为 TTL (Time To Live) 索引 。该索引用于让文档在创建或最后修改一段时间后自动过期并从集合中删除。时间基于被索引字段(通常是日期类型)的值加上指定的秒数。非常适合存储会过期的缓存数据、日志、会话信息等等。⏳💥weights: <document>
:仅用于文本索引 。当你对多个字段创建文本索引时,可以使用weights
指定不同字段的权重,影响搜索结果的相关性评分。权重越高,该字段中的匹配项对评分影响越大。⚖️default_language: <string>
:仅用于文本索引。指定文本索引使用的默认语言,影响分词和词干提取规则。collation: <document>
:在创建字符串字段上的索引时,可以指定排序规则。这样,基于该索引的查询、排序、分组等操作就会按照指定的语言规则进行字符串比较。这对于处理中文、德文等非英文语言非常重要!🌍🗣️
第三节:索引的"类型"------因"材"施"索"!🌳🔑
MongoDB 支持多种索引类型,每种类型都有其特定的用途和优势:
- 单字段索引 (Single Field Index):最简单也最常见的索引,只在一个字段上创建。适用于对单个字段进行频繁查询和排序的场景。
- 复合索引 (Compound Index) :在多个字段 上创建的索引。字段的顺序非常重要! 复合索引首先按照第一个字段的值排序,然后在第一个字段值相同的情况下,再按照第二个字段的值排序,以此类推。
- Prefix Compression (前缀压缩) :复合索引可以支持对索引前缀 字段的查询。比如你在
{ city: 1, age: -1 }
上创建了索引,那么基于{ city: "北京" }
的查询可以使用这个索引,基于{ city: "北京", age: { $gt: 30 } }
的查询也可以使用这个索引。但基于{ age: { $gt: 30 } }
的查询则无法完全使用这个索引(可能只能进行全索引扫描或全集合扫描)。 - ESAR 规则:通常,创建复合索引时,遵循 "Equality, Sort, Array, Range" (相等、排序、数组、范围) 的字段顺序规则,可以最大限度地发挥索引的效率。先放用于相等匹配的字段,再放用于排序的字段,然后是数组字段,最后是用于范围查询的字段。
- Prefix Compression (前缀压缩) :复合索引可以支持对索引前缀 字段的查询。比如你在
- 多键索引 (Multikey Index) :当你对一个数组字段创建索引时,MongoDB 会自动创建多键索引。它会为数组中的每个元素创建独立的索引条目,使得你可以通过数组中的任何元素快速查找包含该元素的文档。非常适合查询包含特定标签、技能等列表的文档。🏷️
- 文本索引 (Text Index) :专门用于支持全文搜索 (Full-Text Search) 。它会对字符串字段进行分词、词干提取等处理,然后建立索引。你可以使用
$text
操作符进行搜索,并可以通过$meta: "textScore"
获取相关性评分。注意一个集合只能有一个文本索引。📖🔍 - 地理空间索引 (Geospatial Index) :用于支持对地理位置数据 的查询。
2dsphere
:基于球面几何,适用于查询地球表面上的位置数据(如经纬度),支持$geoWithin
,$geoIntersects
,$nearSphere
等操作符。推荐使用! 🌍📍2d
:基于平面几何,适用于处理平面坐标系中的位置数据。
- 哈希索引 (Hashed Index) :在字段值的哈希值上建立索引。哈希索引只支持等值匹配查询,不支持范围查询。主要用途之一是用于分片 (Sharding),作为分片键时可以实现更均匀的数据分布。🔑➡️🔢
- TTL 索引 (Time To Live Index):如前所述,基于时间自动删除文档。
- 唯一索引 (Unique Index):如前所述,强制字段值的唯一性。
- 稀疏索引 (Sparse Index):如前所述,只索引存在字段的文档。
理解这些索引类型以及它们的适用场景,是设计高效数据库模式和优化查询性能的关键!🏆
第四节:索引的"体检"------看看我的索引们!👁️🗨️
想知道你的集合上已经创建了哪些索引?就像体检报告一样,看看你的数据库健康状况!🩺
使用 db.collection.getIndexes()
命令。
javascript
use mydatabase;
db.users.getIndexes();
这个命令会返回一个数组,其中包含了该集合上所有索引的详细信息,包括索引名称、键定义、是否唯一、是否稀疏、是否 TTL 等等。
示例输出:
json
[
{
"v" : 2,
"key" : { "_id" : 1 },
"name" : "_id_",
"ns" : "mydatabase.users"
},
{
"v" : 2,
"key" : { "name" : 1 },
"name" : "name_1",
"ns" : "mydatabase.users"
},
{
"v" : 2,
"key" : { "city" : 1, "age" : -1 },
"name" : "city_1_age_-1",
"ns" : "mydatabase.users"
},
// ... 其他索引
]
你可以通过查看这里的 name
字段来获取索引的名称,方便后续删除索引。
第五节:索引的"移除"------清理不再需要的索引!🧹
某个索引不再用于查询了?或者你创建了更优的复合索引,旧的单字段索引可以删除了?就像清理旧书一样,可以删除不再需要的索引。
删除索引使用 db.collection.dropIndex(index)
命令。
index
: 可以是索引名称 (推荐!通过getIndexes()
获取)或者索引键文档。
javascript
use mydatabase;
// 按索引名称删除索引 (假设前面创建了名为 name_city_idx 的索引)
db.users.dropIndex("name_city_idx");
// 按索引键删除索引 (如果你没有给索引起名称)
db.users.dropIndex({ name: 1 });
警告: 删除索引也会影响依赖该索引的查询性能!在删除索引之前,请确保你了解该索引的作用以及删除后可能的影响!不要随意删除生产环境的索引! 💥
第六节:索引的"诊断"------你的查询真的用了索引吗?📊🤔
创建了索引不代表你的查询就一定能飞起来!有时候,你的查询语句或者索引的设计有问题,MongoDB 可能依然会进行全集合扫描!
如何知道你的查询有没有用上索引,以及它是如何执行的?请出 MongoDB 的"X 光机"------.explain()
方法!射线扫过你的查询语句,执行计划一览无余!🩺
在任何查询(find
, aggregate
, updateOne
, deleteOne
等)后面加上 .explain()
方法,就可以看到 MongoDB 执行该操作的详细计划,而不会实际执行操作!
javascript
db.users.find({ city: "北京", age: { $gt: 30 } }).sort({ age: 1 }).explain("executionStats");
explain()
方法可以接受不同的模式 (queryPlanner
, executionStats
, allPlansExecution
),executionStats
模式会实际运行查询一小部分来收集更详细的统计信息,通常用于性能分析。
.explain()
的输出是一个复杂的文档,但最核心的部分是查看 winningPlan
(MongoDB 最终选择的执行计划) 和其中的 stage
(执行阶段)。
stage: "COLLSCAN"
:危险信号!表示进行了全集合扫描!你的查询没有有效地利用索引!🚨stage: "IXSCAN"
:好信号!表示使用了索引扫描!🚀stage: "FETCH"
:表示根据索引找到文档位置后,去拉取完整的文档数据。stage: "SORT"
:如果在IXSCAN
之后出现SORT
阶段,表示索引未能满足排序需求,MongoDB 不得不将结果加载到内存中进行排序,这可能很慢,特别是结果集很大时。如果索引能够覆盖排序字段(复合索引的排序字段在查询字段之后),SORT
阶段可能会被优化掉。
通过分析 .explain()
的输出,你可以了解你的索引是否被正确使用,你的查询语句是否高效,从而进行针对性的优化!这是 MongoDB 性能调优的必修课!🎓
慢查询日志 (Profiler) : MongoDB 还有一个 Profiler 工具,可以记录执行时间超过阈值的操作,帮助你发现慢查询。在 Shell 中可以通过 db.setProfilingLevel(level, slowms)
来开启和配置。
第七节:索引策略与最佳实践------让索引发挥最大效用!🏆
创建索引是门艺术,也是门科学。遵循一些最佳实践,可以让你的索引发挥最大的效用:
- 为常用查询字段创建索引 :所有在
find()
的查询条件、sort()
的排序字段,以及聚合管道中$match
阶段的过滤字段,都是索引的重点关注对象。 - 理解复合索引的顺序 (ESAR):优先放置用于相等匹配的字段,然后是排序字段,接着是数组字段,最后是范围查询字段。这能让一个复合索引支持更多不同类型的查询。
- 考虑覆盖查询 (Covered Queries) :如果你的索引包含了查询需要的所有字段,包括查询条件中的字段和投影中需要的字段,MongoDB 可以直接从索引中返回结果,而无需去拉取原始文档!这称为覆盖查询,速度非常快!⚡️
- 不要创建过多的索引 :索引虽然能提高读性能,但会降低写性能(插入、更新、删除),因为每次写操作都需要更新索引结构。同时,索引也会占用磁盘空间和内存。权衡读写比例,只为真正需要优化的查询创建索引。🔪
- 监控索引的使用情况 :使用
.explain()
或 Profiler 监控索引是否被有效使用。MongoDB 也有命令可以查看索引的使用统计信息。 - 定期审查和优化索引:随着应用的发展和查询模式的变化,定期审查现有的索引是否仍然有效,删除不必要的索引,创建新的索引。
- 在后台创建索引 :对于大型集合,创建索引是一个耗时的操作,可能会阻塞其他数据库操作。在旧版本中,可以使用
background: true
选项在后台创建索引(创建过程中不阻塞读写)。在新版本中,索引创建默认就是在线的,不会阻塞读写(除了某些特定情况)。
创建和管理索引是 MongoDB 性能调优的核心。投入时间和精力去学习和实践索引的使用,会让你的 MongoDB 数据库运行得更顺畅、更高效!🎢
第八节:总结索引操作!🔑💪
太棒了!我们深入了解了 MongoDB 的"加速器"------索引!从它的原理、各种类型、创建、查看、删除,到如何通过 .explain()
诊断索引的使用,以及一些重要的索引策略和最佳实践!
核心要点回顾:
- 索引通过有序结构加速查询和排序。
_id
字段自动带唯一索引。createIndex()
创建索引,可以指定单字段 、复合等类型。- 选项 如
unique
,sparse
,expireAfterSeconds
,weights
,collation
控制索引行为。 - 常见索引类型包括单字段、复合、多键、文本、地理空间、哈希、TTL。
getIndexes()
查看集合上的索引。dropIndex()
删除索引(按名称或键)。.explain()
诊断查询执行计划,查看是否使用了索引 (IXSCAN
vsCOLLSCAN
)。- 最佳实践包括为查询字段建索引、理解复合索引顺序、考虑覆盖查询、避免过多索引等。
掌握了索引,就像给你的查询装上了涡轮增压引擎!💨 现在,你可以开始分析你的应用中的慢查询,并为它们创建合适的索引了!
继续加油!让你的 MongoDB 数据库飞沙走石吧!🚀