MongoDB的索引与排序

304 阅读5分钟

数据库索引背后的基本概念

创建索引没有统一的通用公式,但最有用的方法取决于一些常见的想法。这些共同思想的构建块驻留在散列函数、 B 树和 B + 树数据结构中。

哈希函数是一个定义良好的数学函数,用于将大型(通常是可变大小和复杂的)数据值转换为单个整数或一组字节。哈希函数的输出可以用不同的名称来表示,包括哈希代码、哈希值、哈希和和校验和。散列码通常用作关联数组的键,也称为散列映射。当您将复杂的数据库属性值映射到用于创建索引的哈希代码时,哈希函数非常方便。

树型数据结构以树型结构分布一组值。值是以分层的方式结构化的,在树中的某些节点之间具有链接或指针。二叉树是最多有两个子节点的树: 一个在左边,另一个在右边。一个节点可以是父节点,在这种情况下它最多有两个节点,或者它可以是一个叶子,在这种情况下它是链中的最后一个节点。参见下图了解二叉树数据结构。

image.png

B 树是二叉树的推广。它允许一个父节点有两个以上的子节点。B 树保持数据排序,因此允许有效的搜索和数据访问。B + 树是 B 树的一种特殊变体。在 B + 树中,所有的记录都存储在叶子中,叶子按顺序链接。B +-tree 是用于存储数据库索引的最常见的树结构。

尽管基本构建块是相同的,但是在不同的 NoSQL 产品中创建和应用索引的方式是不同的

MongoDB的索引与排序

MongoDB 围绕索引集合提供了大量丰富的选项,以提高查询性能。默认情况下,它为其包含的所有集合在 _ id 属性上创建一个索引。

为了理解索引的重要性和影响,您还需要一些工具来度量使用和不使用索引的查询性能。

在 MongoDB 中,通过内置的工具来解释查询计划和识别运行缓慢的查询,可以方便地度量查询性能。

查询计划描述数据库服务器运行给定查询所必须执行的操作。首先,在深入研究explain实用程序的输出及其传达的内容之前,运行该实用程序。要获取评级集合中的所有项目,您可以这样查询:

db.ratings.find();

要为这个查询运行explain,您可以运行这个查询:

db.ratings.find ()

explain的结果如下:

{

"cursor" : "BasicCursor",

"nscanned": 1000209,

"nscannedobjects" : 1000209, "n":1000209,

"millis":1549,

"indexBounds":{
	}
}

输出结果显示,返回1,000,209(超过100万)个文档需要1,549毫秒。在查询这1,000,209份文件时,检查了1,000,209个项目。它还声明使用了 BasicCursor。

很明显,解释函数的输出也是一个文档。如上例所示,其属性如下:

  • Cursor-用于返回查询结果集的游标。游标可以有两种类型: 基本游标和 B 树游标。基本游标意味着表扫描和 B 树游标意味着索引被使用。

  • nscanned - 当使用索引时,它对应于索引条目的数量。

  • nscannedobjects - 文件扫描的数量。

  • n-返回的文档数量。

  • Milis-运行查询所花费的时间(毫秒)。

  • IndexBound-表示与查询匹配的最小和最大索引键。只有在使用索引时,此字段才相关。

下一个示例查询评级的子集。评级收集包括一组用户对一组电影的排名(从1到5)。若要筛选评级集合,请将其限制为与特定电影相关的子集。评分集合只有电影 ID,因此要将 ID 与名称关联,需要在电影集合中查找值。

我使用玩具总动员电影作为一个例子。

要获得与 ToyStory 相关的文档,您可以利用一个很好的旧正则表达式。

所有与电影集中的玩具总动员相关的文档都可以这样查询:

db.movies.find({title:/Toy Story/i}); 

运行结果 如下:


{ "_id" : 1, "title" : "Toy Story (1995)", "genres" : [ "Animation", "children's","Comedy"]}

{ "_id" : 3114, "title" : "Toy Story 2 (1999)", "genres" :["Animation", "Children's","Comedy"]}

接下来,获取“玩具总动员”的电影 ID (恰好是1) ,并使用该 ID 查找所有用户的所有相关评级。但是,在此之前,请运行explain函数来查看数据库如何运行正则表达式查询以在电影集合中查找 Toy Story。你可以这样运行explain:

db.movies.find({title: /Toy Story/i}).explain(); 

输出如下:

{"cursor" : "BasicCursor",

 "nscanned":3883,

"nscannedobjects" : 3883,

 "n":2,

"millis" :6,

"indexBounds":{
    
}
 }

在电影集合上运行一个 count,使用 db.animes.count () ; 来验证文档的数量,您会发现它与查询解释的 n 扫描值和 n 扫描对象值相匹配。这意味着正则表达式查询导致了表扫描,这是不高效的。文档数量被限制在3,883个,因此查询仍然足够快,只需要6毫秒。

要列出所有与《玩具总动员》(更准确地说是《玩具总动员》(1995))相关的评级,你可以查询如下:

db.ratings.find({movie_id:1})

要查看前一个查询的查询计划,请按如下方式进行:

db.ratings.find({movie_id: 1}).explain();

产出应如下:



{"cursor" : "BasicCursor", "nscanned" : 1000209,

"nscannedobjects" : 1000209, "n":2077,

"millis":484,

"indexBounds":
             {
    
        }
 }

在这个阶段,很明显查询没有以最佳方式运行,因为 n 扫描和 n 扫描对象计数读取1,000,209,这是集合中的所有文档。这是引入索引和进行优化的好时机。


本文正在参加「金石计划 . 瓜分6万现金大奖」