
总览
- 索引是MongoDB –在数据科学中有效管理和执行数据库查询的关键方面
- 了解如何使用经典的Python库PyMongo在MongoDB中建立索引
介绍
您无法摆脱对数据科学数据库的了解。实际上,我们需要成为数据科学专业人员,非常熟悉如何处理数据库,如何快速执行查询等。只是没有办法!
您应该知道两件事–学习有关数据库管理的所有知识,然后弄清楚如何有效地进行数据库管理。相信我,您将在数据科学领域大有帮助。
MongoDB是一种流行的NOSQL数据库,旨在简化开发和扩展。它能够高效地处理大量数据(当今数据驱动世界中的必备功能)。那么MongoDB如何去做呢?

一个词-索引!
与其他任何数据库一样,MongoDB也提供对索引数据的支持。MongoDB中的索引使我们能够以更快的速度获取文档,从而提高了数据库的性能。当数据库处理海量数据(可能以太字节为单位)时,这非常重要!
MongoDB具有具有各种属性的不同索引类型,这些属性可以处理复杂的查询。可以根据需要以及在文档中任何类型的字段上创建和删除索引。
因此,在本文中,我们将研究MongoDB中的索引以及何时适当使用索引。
目录
- 什么是索引?
- 连接到MongoDB Atlas
- 使用PyMongo访问数据
- 在MongoDB中建立索引
- MongoDB集合索引
- 说明查询结果
- 创建单个字段索引
- 下降索引
- 复合索引
- 多键索引
- 文字索引
- 地理空间指数
- 索引属性
- 唯一索引
- 部分索引
什么是索引?
我确定您已完成此操作–仅通过查看索引即可立即跳到书中的相关页面。这也是数据库中索引的感觉。只需查看索引,我们就可以立即跳到内存中的适当位置,而不必查看数据库中的所有其他数据。索引使我们能够减少数据库扫描以查找特定文档所需的文档数量,从而突飞猛进地提高数据库性能。
例如,如果您有一个存储有关公司员工信息的数据库,并且经常根据他们的部门字段查询该数据库,那么在部门字段上创建索引是明智的。数据库将按顺序排列部门的字段值。现在,每当您尝试基于部门进行查询时,数据库将只简单地首先遍历索引,跳至相关记录所在的位置,然后检索文档。简单来说,这就是索引的工作方式。
但是,为了正确使用索引,您需要知道将在数据库上执行的查询。否则,创建随机索引将浪费资源。
如果在上面的示例中,我们在雇员的名字而不是部门的名字上创建了索引,那么它就没有用了,因为数据库仍然必须遍历所有文档。因此,了解要编制索引的查询很重要。
创建索引的一个缺点是,它在每次插入,更新和删除时都会增加一点开销。这是因为数据库不仅需要执行该操作,而且还需要在集合上的任何索引中进行记录。因此,应该创建索引的绝对最小数量。
现在,让我们看看如何在MongoDB中执行索引。但是首先,我们需要加载数据集。
连接到MongoDB Atlas
MongoDB Atlas是一项全球云数据库服务。使用它,我们可以跨AWS,Azure或GCP部署完全托管的MongoDB,并创建真正灵活且可扩展的数据库。
我们将使用MongoDB Atlas集群可用的样本数据集。因此,在动手使用MongoDB中的索引之前,我们需要先创建一个MongoDB Atlas帐户,然后创建一个免费集群以访问示例数据集。
- 创建MongoDB Atlas帐户非常简单。所有您需要做的就是在这里注册您的帐户并登录到您的帐户
- 然后,您需要部署一个免费层集群。这些永不过期,但每个项目只能创建一个
- 现在,您需要将IP地址列入白名单,以便可以连接到集群并访问数据库
- 接下来,您需要创建一个用户来访问集群。提供用户名和密码
- 现在您需要连接到集群
- 注意:我的Python版本是3.7.4,但是从“版本”下拉列表中选择“ 3.6或更高版本”选项给我一个错误。选择“ 3.4或更高版本”有效。如果遇到错误,可以尝试一下。
一旦有了连接字符串,请启动Jupyter笔记本,因为我们将需要PyMongo库!使用PyMongo访问数据
要从您的本地计算机访问数据,我们将需要PyMongo库。您将需要生成的连接字符串才能连接到群集。切记将<username>和<password>替换为您为数据库用户创建的用户名和密码。

现在,您可以使用以下命令查看Mongo提供的所有示例数据库:

我们将在此处使用“ sample_restaurants”数据库。使用以下命令加载数据库:

您可以查看此数据库中的所有集合:

您可以计算每个集合中的文档数:

以下是每个集合中的文档:




在MongoDB中建立索引
没有索引,MongoDB数据库必须扫描集合中的每个文档以查找相关文档。这称为COLLSCAN或集合扫描。但是,使用索引大大减少了MongoDB需要扫描的文档数量。因此,使用索引可以使查询的执行效率很高,尤其是在数据库存储大量文档的情况下,MongoDB非常流行。
MongoDB提供了它支持的许多不同的索引类型,以及为每个索引提供的各种属性。因此,让我们探索这些索引,最好使用它们!
MongoDB集合索引
每个MongoDB集合都有一个默认索引:_id。这是在创建集合时创建的。确保集合中没有两个文档包含重复值。
要知道一个集合中有多少个索引以及关于它们的其他相关信息,我们使用index_information()函数。
如果我们现在执行它,它将仅返回_id索引,因为它是迄今为止集合中唯一的索引。实际上,即使您从集合中删除自定义索引,您仍将保留此默认索引。
以下是餐厅集合的索引:


它将结果作为字典返回。关键字是索引名称,_id _代表_id索引。这些值是包含有关索引信息的字典。
同样,如果我们检索邻域集合的索引,则将获得默认索引:


说明结果
当我们在MongoDB中运行查询时,实际上可以使用describe()函数确定有关该查询的很多信息。
它返回一个文档,其中包含有关查询计划和执行统计信息。我们对执行统计数据感兴趣。
执行统计信息包含有关MongoDB如何检索查询结果的大量信息。但是首先,让我们看一下explain()函数返回的文档:


在这里,重点关注包含执行统计信息的executionStats键。以下是重点关注的重要键:
- 说明.executionStats.nReturned返回与查询匹配的文档数
- describe.executionStats.executionTimeMillis返回选择查询计划和执行查询所需的总时间(以毫秒为单位)
- describe.executionStats.totalKeysExamined返回扫描的索引条目数
- describe.executionStats.totalDocsExamined返回查询执行期间检查的文档数。这些不是返回的文件
explain.executionStats阶段描述了该操作。COLLSCAN表示集合扫描,而我们稍后将看到的IXSCAN表示索引键扫描。
在此查询中,您会注意到“ executionStats.executionStages.stage”正在使用COLLSCAN。这意味着默认的_id索引用于扫描所有集合。另外,请注意,explain.executionStats.nReturned和explain.executionStats.totalDocsExamined与集合中的文档数相同。这是因为我们使用的是默认索引。但是,当我们使用索引时,这些值将改变。
因此,事不宜迟,让我们在MongoDB中创建我们的第一个索引!
创建单个字段索引
假设我们要查找所有提供美国美食的餐厅的记录。让我们尝试使用MongoDB find()函数来发现这一点:


现在,我们可以使用explain()函数来获取有关该查询的统计信息。具体来说,我们将看看executionStats键:


如您所见,它花了10毫秒,MongoDB必须扫描所有25,359个文档,而由于使用了集合扫描,它仅返回了6,185个文档。我们可以使查询更快吗?
是的,我们可以使用索引来做到这一点。
由于我们正在查询美食键,因此可以在其上创建一个新索引。我们可以通过使用create_index()函数来做到这一点:


如您所见,我们现在有两个索引-默认索引_id和自定义索引Cuisine_1。由于我们创建了单个字段的索引,因此将其称为Single Field Index。

单个字段索引如何排序值的视图
现在,我们将这个索引用于上一个查询,并查看统计信息:


注意这两个查询的“ executionStats.totalDocsExamined”值。在使用默认索引的前一个查询中,必须扫描所有25,359条记录以查找相关记录。但是,当我们创建索引时,仅扫描了6,183条记录。如“ executionStats.executionTimeMillis”值所示,这可以大大减少从数据库查询的时间。
另外,请注意,此处的“ executionStats.executionStages.stage”是IXSCAN而不是COLLSCAN,因为MongoDB已扫描了我们生成的索引键。
您甚至可以在创建时命名索引。该CREATE_INDEX()有名字的说法,你可以提供您的自定义名称。
让我们创建一个新索引,因为用不同的名称再次创建相同的索引会给您一个错误。因此,我将使用borough密钥创建一个新索引,并将其命名为borough_index(非常有创意!):


在这里,请注意,就像我们希望的那样,新索引被引用为“ borough_index”。现在,让我们看看如何在继续使用MongoDB中的复合索引之前删除索引。
下降索引
创建索引的缺点在于,它在每次插入,更新和删除操作时都会产生一些开销。这是因为数据库需要执行操作,然后还要在集合的索引中记下它。因此,重要的是在毫无用处的MongoDB中删除索引。这将使集合具有更多的可用空间,并可以执行更快的写入。
我们可以像创建索引一样轻松地删除索引。您必须在drop_index()函数中提供要删除的索引的名称:


在这里,我们删除了自定义索引“ cuisine_1”。您还可以使用drop_indexes()函数通过一个命令删除集合的所有自定义索引:


您只能删除我们创建的所有自定义索引。
不能删除默认的_id索引。如果尝试这样做,将会收到错误消息:


现在,让我们看看如何在MongoDB中创建复合索引!
复合索引
假设您在多个字段上查询集合,然后可以创建一个包含查询中所有字段的复合索引,而不是创建单个索引。
这是创建复合索引的语法:db.collection.createIndex({<field1>:<type>,<field2>:<type2>,…})。

复合索引如何排序值的视图MongoDB对任何复合索引施加32个字段的限制。
现在,如果我们要查询集合以基于特定自治市的美食来检索文档,我们可以创建一个包含这两个字段的复合键。首先,让我们看一下集合中所有独特的美食和行政区。

我们可以在创建索引之前运行查询以比较数据库的性能:


不出所料,我们正在使用COLLSCAN并必须扫描所有25,359个文档,而我们仅返回了与查询匹配的152个相关文档。
现在,我们可以使用自治市镇和美食领域创建复合索引:


将包含文件引用该指数第一排序由值市镇场,并且在每个值内市镇场,排序由值美食领域。即“布朗克斯”,“阿富汗”;“布朗克斯”,“非洲”;等等
您必须已经注意到,我们现在将字段作为元组提供,其中第一个值是字段的名称,而第二个值对我们来说是忽略的。好吧,第二个值确定索引方向。这意味着我们可以根据项目的索引值对其进行排序。由于这里我们对两个字段都使用升序,因此自治市镇将按AZ字母顺序排序,并且在每个自治市镇值内,美食也将按AZ字母顺序排序。例如:
“布朗克斯”,“阿富汗”;…;“布朗克斯”,“素食主义者”
。
。
。
“史泰登岛”,“阿富汗”;…;“史泰登岛”,“素食主义者”
但是,如果我们对自治市镇使用 ASCENDING,对美食进行 DESCENDING,则该索引将按自治市镇 AZ的顺序排序,其中的美食按ZA字母顺序排列,如下所示:
“布朗克斯”,“素食主义者”;…;“布朗克斯”,“阿富汗”
。
。
。
“史泰登岛”,“素食主义者”;…;“史泰登岛”,“阿富汗”
此类索引的代码为:

现在,让我们尝试运行与以前相同的查询,并注意不同之处:


在这里,MongoDB使用我们创建的“ borough_cuisine”索引在1毫秒内检索结果,它仅需扫描152个文档,这也等于返回的文档数。因此,我们能够通过创建复合索引来优化查询。
但是,除了支持匹配所有索引字段的查询外,复合索引还可以支持匹配复合索引字段的前缀子集的查询。也就是说,我们可以只查询市镇 除了上询问市镇联合美食,因为我们已经完成的工作。
让我们使用索引查询来查找“曼哈顿”中的餐馆:


如您所见,MongoDB使用“ borough_cuisine”索引来扫描相关文档。
接下来,让我们看看如何在MongoDB中创建多键索引。
多键索引
我们甚至可以在包含数组值的字段上创建索引。这些称为多键索引。MongoDB为数组中的每个元素创建一个索引键。它是由MongoDB自动创建的,因此您不必显式指定它。
这些Multikey索引可以在既包含标量值(既不是嵌入式文档也不是数组值,如字符串,数字)和嵌套文档的数组上构造。

多键索引如何排序值的视图
但是在创建多键索引之前,让我们首先删除到目前为止已创建的索引。

现在,如前所述,您可以为基本索引创建多键索引,例如:
{_id:1,a:[1,2],b:[1,2]}
或在包含嵌套对象的数组字段上创建多键索引,例如:
{_id:1,a:[{'score':4,'grade':'A'},{'score':2,'grade':'B'}],b:“ ABC”}
现在,由于这里的集合具有数组格式的嵌套对象,因此我们将使用它来创建多键索引。我们将为餐厅集合中的“ grades.grade”字段创建一个多键索引。

首先,我们将查看我们要运行的查询的执行统计信息。我想找出评论中Z级的餐厅。


您会注意到,MongoDB正在扫描所有收集文档,并在28毫秒内返回了结果。现在,让我们尝试在此字段上创建一个多键索引,并注意查询优化中的更改。


查询的执行统计信息。


您会注意到,MongoDB能够在4毫秒内检索文档,而最初的时间是28毫秒。此外,它只需要检查1337个文档。
文字索引
现在,正则表达式对于匹配文本字段内的精确值很有用。但是,如果您要在文本字段中匹配特定的单词,则应使用Text index。
让我们看一看餐厅集合中的一个文档。

如果要检索名称中带有“ Kitchen”一词的所有餐厅,则可以使用文本索引轻松地做到这一点。


现在,如果要检索名称中带有关键字“ Kitchen”的所有餐厅,我们只需编写以下查询即可。


您甚至可以在名称中搜索多个关键字。以下查询将返回名称为“ Chinese”或“ Kitchen”的那些文档。


您甚至可以否定包含特定关键字的某些文档。例如,以下查询将检索其中包含关键字“ Chinese”而不是关键字“ Restaurant”的所有文档。


您可以在MongoDB中使用文本索引做更多的事情,我恳请您检查一下。
地理空间指数
由于移动设备的激增,近来基于位置的数据已被广泛使用。这意味着查找最接近某个位置的位置是当今最常见的查询。为了有效地处理此类查询,MongoDB提供了用于查询坐标的地理空间索引。
让我们看一下集合中的文档。

餐厅的集合包含餐厅的坐标。

而邻里集合包含餐厅邻里的维度。我们可以为两个字段创建地理空间索引。首先,让我们为餐厅创建坐标。
所述2D索引被用来创建一个二维平面指数。


$ near返回地理空间索引上从最近到最远的文档。


以下查询返回距指定坐标至少10米,距最大坐标1000米(从最近到最远排序)的文档。


在这里,根据与给定位置的接近程度,我们仅返回了11个文档。现在让我们为邻域集合创建地理空间坐标。
邻域集合中的坐标是类似地球的对象。您一定已经注意到附近的形状也随坐标一起提供。因此,我们需要创建一个2dsphere索引,以支持在类似地球的球体上计算几何形状的查询。


要查询2dsphere索引,我们需要使用$ geoWithin运算符。它返回的地理空间数据完全存在于指定位置数据中的文档。


由于我们指定的地理空间形状内没有文档,因此返回了0个文档。
现在让我们讨论到目前为止我们已经看过的这些索引类型的一些属性。
索引属性
就像MongoDB中有许多不同的索引一样,这些索引也有许多属性。在这里,我们看一些这些属性。
唯一索引
到目前为止,我们创建的索引不是唯一的。这意味着对于给定的索引值,可能有多个文档。但是,唯一索引可确保对于给定的字段,集合中的每个文档都将具有唯一的值。例如,如果要确保“用户名”字段中没有两个文档可以具有相同的值,则可以创建唯一索引。
您可能已经熟悉的唯一索引是“ _id”上的索引,该索引在创建集合时创建。
找到邻居记录,我们可以为邻居名称创建唯一索引。https:///aniruddha27/781fd3f2cb3dd732d264861e1cf669e5

现在,如果我们尝试为“名称”字段插入重复的值,则会收到错误消息。

我们甚至可以创建唯一的复合索引。
我们可以从餐厅的集合中为美食和行政区创建美食。尽管我们很可能会收到重复的键错误,因为很可能在同一行政区中有两家餐厅提供相同的美食。

同样,也可以创建唯一的多键索引。

我们得到相同的重复键错误,因为索引字段已经存在重复值。
好了,现在让我们看一下部分索引。
部分索引
有时我们可能不想索引集合中的所有文档,而只是索引一部分文档。那就是部分索引出现的地方。
部分索引仅索引集合中满足指定过滤器表达式的那些文档。这降低了存储要求,并减少了索引的创建和维护所涉及的间接费用。
假设我们只想根据“ grade.score”大于5的那些餐馆中提供的美食来查询文档。在这种情况下,我们无需在整个“ grades.score”字段上创建索引,创建部分索引。
在创建索引之前,让我们首先查看查询的执行统计信息。

要使用部分索引属性,我们需要使用 create_index() partialFilterExpression参数,并提供将指定文档子集的过滤条件。
我们将为 美食领域并且由于我们要查询得分大于5的餐厅,因此我们可以将其作为过滤条件。

以下查询使用索引,因为查询谓词包括条件grades.score:{$ gt:7},该条件与由索引过滤器表达式grades.score:{$ gt:5}匹配的文档子集匹配:



检索文档只用了1毫秒。
以下查询不使用索引,因为grades.score:{$ gt:2}而索引具有过滤器grades.score:{$ gt:5}。也就是说,此查询将匹配比索引更多的文档,因此它将不使用我们创建的索引。


如您所见,查询使用COLLSCAN而不是索引来检索文档。
以下查询也不能使用部分索引,因为查询谓词不包含过滤器表达式,并且使用索引将返回不完整的结果集。


如您所见,使用了COLLSCAN并检查了所有记录的查询。
尾注
我希望本文能使您对数据库中索引的重要性以及MongoDB如何使创建每种可能的查询模式的索引变得非常容易这样一个好主意。
创建索引时要记住的重要一点是,您应该根据查询创建索引。另外,在创建索引时,请确保索引本身不会太大而不能容纳在RAM中。这将确保数据库不必从磁盘读取索引,这肯定会提高性能。最后,明智地创建索引,以便您创建的索引使数据库能够扫描最少的文档。
















