MongoDb索引
数据库索引类似于图书索引。有了索引便不需要浏览整本书,而是可以采取一种快捷方式,只查看一个有内容引用的有序列表。这使得 MongoDB 的查找速度提高了好几个数量级。
不使用索引的查询称为集合扫描,这意味着服务器端必须“浏览整本书”才能得到查询的结果。
MongoDB如何选择索引
现在来看一下 MongoDB 是如何选择索引来满足查询的。假设有 5 个索引。当有查询进来时,MongoDB 会查看这个查询的形状。这个形状与要搜索的字段和一些附加信息(比如是否有排序)有关。基于这些信息,系统会识别出一组可能用于满足查询的候选索引。
假设有一个查询进入,5 个索引中的 3 个被标识为该查询的候选索引。然后,MongoDB 会创建 3 个查询计划,分别为每个索引创建 1 个,并在 3 个并行线程中运行此查询,每个线程使用不同的索引。这样做的目的是看哪一个能够最快地返回结果。形象化地说,可以将其看作一场竞赛。这里的设计是,到达目标状态的第一个查询计划成为赢家。但更重要的是,以后会选择它作为索引,用于具有相同形状的其他查询。每个计划会相互竞争一段时间(称为试用期),之后每一次竞赛的结果都会用来在总体上计算出一个获胜的计划。
要赢得竞赛,查询线程必须首先返回所有查询结果或按排序顺序返回一些结果。考虑到在内存中执行排序的开销,其中排序的部分非常重要。
让多个查询计划相互竞争的真正价值在于,对于具有相同形状的后续查询,MongoDB 会知道要选择哪个索引。服务器端维护了查询计划的缓存。一个获胜的计划存储在缓存中,以备在将来用于进行该形状的查询。随着时间的推移以及集合和索引的变化,查询计划可能会从缓存中被淘汰。而 MongoDB 会再次进行尝试,以找到最适合当前集合和索引集的查询计划。其他会导致计划从缓存中被淘汰的事件有:重建特定的索引、添加或删除索引,或者显式清除计划缓存。此外,mongod 进程的重启也会导致查询计划缓存丢失。
复合索引
在设计复合索引时:
- 等值过滤的键应该在最前面;
- 用于排序的键应该在多值字段之前;
- 多值过滤的键应该在最后面
选择键的方向
为了在不同方向上优化这个复合排序,需要使用与方向相匹配的索引。
{"age" : 1, "username" : -1}
使用覆盖查询
为了确保查询只使用索引就可以完成,应该使用投射来避免返回 "_id" 字段
隐式索引
$运算符如何使用索引
低效的运算符
通常来说,取反的效率是比较低的。"$ne" 查询可以使用索引,但不是很有效。由于必须查看所有索引项,而不只是 "$ne" 指定的索引项,因此基本上必须扫描整个索引。
"$not" 有时能够使用索引,但通常它并不知道要如何使用。它可以对基本的范围,比如将{"key" : {"$lt" : 7}}
变为 {"key" : {"$gte" : 7}}
和正则表达式进行反转。然而,大多数使用 "$not" 的查询会退化为全表扫描 1。而 "$nin" 总是使用全表扫描。
范围
当设计基于多个字段的索引时,应该将用于精确匹配的字段(如 "x" : 1)放在最前面,将用于范围匹配的字段,如
"y": {"$gt" : 3, "$lt" : 5}
放在最后面。这样可以使查询先用第一个索引键进行精确匹配,然后再用第二个索引范围在这个结果集内部进行搜索。
OR查询
MongoDB 在一次查询中仅能使用一个索引。也就是说,如果在 {"x" : 1} 上有一个索引,在 {"y" : 1} 上有另一个索引,然后在 {"x" : 123, "y" : 456} 上进行查询时,MongoDB 会使用其中一个索引,而不是两个一起使用。唯一的例外是 "$or",每个"$or" 子句都可以使用一个索引,因为实际上 "$or" 是执行两次查询然后将结果集合并
通常来说,执行两次查询再将结果合并的效率不如单次查询高,因此应该尽可能使用 "$in" 而不是 "$or"
索引对象和数组
MongoDB 允许深入文档内部,对内嵌字段和数组创建索引。内嵌对象和数组字段可以和顶级字段一起在复合索引中使用。尽管在某些方面比较特别,但是它们的大多数行为与“普通”索引字段是一致的。
索引内嵌文档
可以在内嵌文档的键上创建索引,方法与在普通键上创建索引相同。如果有一个集合,其中每个文档表示一个用户,那么可能会有一个描述每个用户位置的内嵌文档:
{
"username" : "sid",
"loc" : {
"ip" : "1.2.3.4",
"city" : "Springfield",
"state" : "NY"
}
}
可以在 "loc" 的其中一个子字段(如 "loc.city")上创建索引,以提高这个字段的查询速度.
注意,对内嵌文档本身(如 "loc")创建索引的行为与对内嵌文档的某个字段(如 "loc.city")创建索引的行为非常不同。对整个子文档创建索引只会提高针对整个子文档进行查询的速度。只有在进行与子文档字段顺序完全匹配的查询时(比如 db.users.find({"loc" : {"ip" : "123.456.789.000", "city" : "Shelbyville", "state" : "NY"}}})
,查询优化器才能使用 "loc" 上的索引。而对于 db.users.find({"loc.city" : "Shelbyville"})
这样的查询是无法使用索引的。
索引数组
也可以对数组创建索引,这样就能高效地查找特定的数组元素了。
假设有一个博客文章集合,其中每个文档是一篇文章。每篇文章都有一个 "comments" 字段,这是一个由 "comment" 子文档组成的数组。如果想找出最近被评论次数最多的博客文章,可以在博客文章集合中内嵌的 "comments" 数组的 "date" 键上创建索引
db.blog.createIndex({"comments.date" : 1})
索引基数
基数(cardinality)是指集合中某个字段有多少个不同的值。
一个字段的基数越高,这个字段上的索引就越有用。这是因为这样的索引能够迅速将搜索范围缩小到一个比较小的结果集。对于基数比较低的字段,索引通常无法排除大量可能的匹配项。
应该在基数比较高的键上创建索引,或者至少应该把基数比较高的键放在复合索引的前面
explain输出
- "isMultiKey" :
- 本次查询是否使用了多键索引。
- "nReturned" :
- 本次查询返回的文档数量。
- "totalDocsExamined" :
- MongoDB 按照索引指针在磁盘上查找实际文档的次数。如果查询中包含的查询条件不是索引的一部分,或者请求的字段没有包含在索引中,MongoDB 就必须查找每个索引项所指向的文档。
- "totalKeysExamined" :
如果使用了索引,那么这个数字就是查找过的索引条目数量。如果本次查询是一次全表扫描,那么这个数字就表示检查过的文档数量。 - "stage" : "IXSCAN"
- MongoDB 是否可以使用索引完成本次查询。如果不可以,那么会使用 "COLLSCAN" 表示必须执行集合扫描来完成查询。
- "needYield" : 0
- 为了让写请求顺利进行,本次查询所让步(暂停)的次数。如果有写操作在等待执行,那么查询将定期释放它们的锁以允许写操作执行。在本次查询中,由于并没有写操作在等待,因此查询永远不会进行让步。
- "executionTimeMillis" : 15
- 数据库执行本次查询所花费的毫秒数。这个数字越小越好。
- "indexBounds" :
- 这描述了索引是如何被使用的,并给出了索引的遍历范围。
何时不使用索引
索引在提取较小的子数据集时是最高效的,而有些查询在不使用索引时会更快。结果集在原集合中所占的百分比越大,索引就会越低效,因为使用索引需要进行两次查找:一次是查找索引项,一次是根据索引的指针去查找其指向的文档。而全表扫描只需进行一次查找:查找文档。在最坏的情况下(返回集合内的所有文档),使用索引进行查找的次数会是全表扫描的两倍,通常会明显比全表扫描慢。
索引类型
唯一索引
唯一索引确保每个值最多只会在索引中出现一次。
如果一个键不存在,那么索引会将其作为 null 存储。这意味着如果对某个键创建了唯一索引并试图插入多个缺少该索引键的文档,那么会因为集合中已经存在了一个该索引键值为 null 的文档而导致插入失败。
在某些情况下,一个值可能不会被索引。索引桶(index bucket)的大小是有限制的,如果某个索引项超过了它的限制,这个索引项就不会被包含在索引中。这可能会造成一些困惑,因为这会使一个文档对使用此索引的查询“不可见”。在 MongoDB 4.2 之前,索引中包含的字段必须小于 1024 字节。在 MongoDB 4.2 及以后的版本中,这个限制被去掉了。
如果一个文档的字段由于大小限制不能被索引,那么 MongoDB 就不会返回任何类型的错误或警告。这意味着大小超过 8KB 的键不会受到唯一索引的约束:比如,你可以插入多个相同的 8KB 字符串。
部分索引
唯一索引会将 null 作为值,因此无法在多个文档缺少键的情况下使用唯一索引。然而,在很多情况下,你可能希望仅在键存在时才强制执行唯一索引。如果一个字段可能存在也可能不存在,但当其存在时必须是唯一的,那么可以将 "unique" 选项与"partial" 选项组合在一起使用
MongoDB 中的部分索引只会在数据的一个子集上创建。这与关系数据库上的稀疏索引不同,关系数据库创建的指向一个数据块的索引项会更少,不过所有数据块都有一个关联的稀疏索引项。
要创建部分索引,需要包含 "partialFilterExpression" 选项。部分索引提供了稀疏索引功能的超集,使用一个文档来表示希望在其上创建索引的过滤器表达式。
如果有一个电子邮件地址字段是可选的,但是如果提供了这个字段,那么它的值就必须是唯一的。我们可以这样做
db.users.ensureIndex({"email" : 1}, {"unique" : true, "partialFilterExpression" : { email: { $exists: true } }})
标签:创建,MongoDB,使用,查询,索引,文档,MongoDb
From: https://www.cnblogs.com/zpf253/p/18220639