【MongoDB】MongoDB查询语句find的使用,和提高查询速度的游标的使用,非常详细!!!

在这里插入图片描述

😁 作者简介:一名大四的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:MongoDB数据库
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气

​🔥​前言:

这里是关于MongoDB中查询语句find的使用,其中对于特定类型的查询非常特别,还有游标的使用可以加快我们的查询速度,这是我的学习MongoDB笔记,希望可以帮助到大家,欢迎大家的补充和纠正

第4章 查询

4.1 find简介

4.1.1 指定要返回的键

概念:find方法中的第二个参数就是投影条件,投影条件是一个文档,其中键是要包含或排除的字段,对应的值为 1 表示包含,0 表示排除。默认情况下,如果不指定投影条件,MongoDB 会返回文档中的所有字段
案例:

  1. 默认情况下,如果不指定投影条件,MongoDB 会返回文档中的所有字段
> db.users.find({}, {"username" : 1, "email" : 1})
{
    "_id" : ObjectId("4ba0f0dfd22aa494fd523620"),
    "username" : "joe",
    "email" : "joe@example.com"
}

2.可能在文档中有很多键,而你不希望结果中包含 “fatal_weakness” 键:

> db.users.find({}, {"fatal_weakness" : 0})

3.要将“_id“键从返回结果剔除

> db.users.find({}, {"username" : 1, "_id" : 0})
{
    "username" : "joe"
}

4.3 特定类型查询

4.3.3 查询数组之$slice操作符

注意点:除非特别指定,否则在使用 “$slice” 时会返回文档中的所有键。这与其他的键指定符不同,后者不会返回未指定的键。例如,有这样一个关于博客文章的文档:
例子:

{
    "_id" : ObjectId("4b2d75476cc613d5ee930164"),
    "title" : "A blog post",
    "content" : "...",
    "comments" : [
        {
            "name" : "joe",
            "email" : "joe@example.com",
            "content" : "nice post."
        },
        {
            "name" : "bob",
            "email" : "bob@example.com",
            "content" : "good post."
        }
    ]
}

> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -1}})
{
    "_id" : ObjectId("4b2d75476cc613d5ee930164"),
    "title" : "A blog post",
    "content" : "...",
    "comments" : [
        {
            "name" : "bob",
            "email" : "bob@example.com",
            "content" : "good post."
        }
    ]
}

即使title和content没有显式地被包含在键指定符中,但它们依然被返回了

4.3.5 查询数组之数组与范围查询的相互作用

文档中的标量(非数组元素)必须与查询条件中的每一条子句相匹配。如果使用 {“x” : {“ g t " : 10 , " gt" : 10, " gt":10,"lt” : 20}} 进行查询,那么 “x” 必须同时满足大于 10 且小于 20。然而,如果文档中的 “x” 字段是一个数组,那么当 “x” 键的某一个元素与查询条件的任意一条语句相匹配(查询条件中的每条语句可以匹配不同的数组元素)时,此文档也会被返回。
接下来我们可以来看一个例子:
现在有一个文档

{"x" : 5}
{"x" : 15}
{"x" : 25}
{"x" : [5, 25]}

如果想找出 “x” 的值在 10 和 20 之间的所有文档,那么你可能会本能地构建这样的查询,即 db.test.find({“x” : {“ g t " : 10 , " gt" : 10, " gt":10,"lt” : 20}}),然后期望它会返回一个文档:{“x” : 15}。然而,当实际运行时,我们得到了两个文档,如下所示:

> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}})
{"x" : 15}
{"x" : [5, 25]}

5 和 25 都不在 10 和 20 之间,但由于 25 与查询条件中的第一个子句("x"的值大于 10)相匹配,5 与查询条件中的第二个子句(“x” 的值小于 20)相匹配,因此这个文档会被返回。
😐 这样就使得针对数组的范围查询基本失去了作用:一个范围会匹配任何多元素数组,有几种方法可以获得预期的行为
1.可以使用 e l e m M a t h 强制 M o n g o D B 将这两个子句与单个数组元素进行比较,不过 elemMath强制MongoDB将这两个子句与单个数组元素进行比较,不过 elemMath强制MongoDB将这两个子句与单个数组元素进行比较,不过elemMath不会匹配非数组元素

> db.test.find({"x" : {"$elemMatch" : {"$gt" : 10, "$lt" : 20}}})
> // 没有结果

文档 {“x” : 15} 不再与查询条件匹配了,因为它的 “x” 字段不是一个数组。也就是说,你应该有充分的理由在一个字段中混合数组和标量值,而这在很多场景中并不需要。对于这样的情况,“ e l e m M a t c h " 为数组元素的范围查询提供了一个很好的解决方案。 ∗ ∗ 2. 如果在要查询的字段上有索引(参见第 5 章),那么可以使用 m i n 和 m a x 将查询条件遍历的索引范围限制为 " elemMatch" 为数组元素的范围查询提供了一个很好的解决方案。 **2.如果在要查询的字段上有索引(参见第 5 章),那么可以使用 min 和 max 将查询条件遍历的索引范围限制为 " elemMatch"为数组元素的范围查询提供了一个很好的解决方案。2.如果在要查询的字段上有索引(参见第5章),那么可以使用minmax将查询条件遍历的索引范围限制为"gt” 和 “$lt” 的值**

> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}}).min({"x" : 10}).max({"x" : 20})
{"x" : 15}

现在,这条查询语句只会遍历值在 10 和 20 之间的索引,不会与值为 5 和 25的这两个条目进行比较。但是,只有在要查询的字段上存在索引时,才能使用min 和 max,并且必须将索引的所有字段传递给 min 和 max
🍁在查询可能包含数组的文档的范围时,使用 min 和 max 通常是一个好主意。在整个索引范围内对数组使用 “ g t " / " gt"/" gt"/"lt” 进行查询是非常低效的。它基本上接受任何值,因此会搜索每个索引项,而不仅仅是索引范围内的值。

4.4 $where查询

概念: 允许你在查询中执行任意的 JavaScript 代码

例子:

> db.foo.find({"$where" : function () {
... for (var current in this) {
...     for (var other in this) {
...         if (current != other && this[current] == this[other]) {
...             return true;
...         }
...     }
... }
... return false;
... }});

注意:除非绝对必要,否则不应该使用 “$where” 查询:它们比常规查询慢得多

4.5 游标

概念:游标(Cursor)是用于遍历查询结果的对象,你执行查询时,MongoDB 返回一个游标,它指向查询结果的初始位置。通过游标,你可以逐步获取结果集中的文档,而不需要一次性将整个结果集加载到内存中。
案例:

> for(i=0; i<100; i++) {
...     db.collection.insertOne({x : i});
... }
> var cursor = db.collection.find();

> while (cursor.hasNext()) {
...     obj = cursor.next();
...     // 执行任务
... }

//还有一种用法,游标cursor类还实现了Js的迭代器接口,因此可以使用forEach
> var cursor = db.people.find();
> cursor.forEach(function(x) {
...     print(x.name);
... });
adam
matt
zak

上述案例中,find方法就返回一个游标,cursor.hasNext() 会检查是否有后续结果存在,而 cursor.next() 用来对其进行获取。
游标的工作机制:

  1. **查询请求:**当客户端发起一个查询请求,MongoDB服务器接受请求
  2. 返回游标:服务器会返回一个包含初始查询结果的游标给客户端,这是,并不会立刻返回所有匹配的文档,而是返回一个包含部分结果的游标对象,这个部分结果一般来说是100个结果或者4MB的数据(两者中较小者)
  3. 分批获取:客户端可以使用游标的方法(如next)来逐批获取文档。当客户端请求下一批文档时,服务器会在需要时继续执行查询,获取更多的文档,并返回给客户端。
  4. **游标生命周期:**获取的过程会一直持续,知道游标耗尽或者结果被全部返回

4.5.1 limit,skip和sort

  1. limit:会限制返回结果的数量,传入的参数只能是正数,负数会报错
  2. skip:会跳过指定数量的文档,然后返回剩下的文档,参数也是正数
  3. sort:会接受一个对象作为参数,这个对象是一组键–值对,键对应文档的键名,值对应排序的方向。排序方向可以是 1(升序)或 -1(降序)。如果指定了多个键,则结果会按照这些键被指定的顺序进行排序。例如,要按照 “username” 升序及 “age” 降序排列

案例:实现分页,假设你正在运营一个在线商店,有人想搜索 mp3。如果想每页返回 50 个结果并按照价格从高到低排序

> db.stock.find({"desc" : "mp3"}).limit(50).sort({"price" : -1})

//如果顾客单机下一页
> db.stock.find({"desc" : "mp3"}).limit(50).skip(50).sort({"price" : -1})

然而,略过大量的结果会导致性能问题
比较顺序
MongoDB 对于类型的比较有一个层次结构。有时一个键的值可能有多种类型:整型和布尔型,或者字符串和 null。如果对混合类型的键进行排序,那么会有一个预定义的排序顺序。从最小值到最大值,顺序如下。

  • 最小值
  • null
  • 数字(整型、长整型、双精度浮点型、小数型)
  • 字符串
  • 对象/文档
  • 数组
  • 二进制数据
  • 对象 ID
  • 布尔型
  • 日期
  • 时间戳
  • 正则表达式
  • 最大值

4.5.2 避免略过大量结果

使用 skip 来略过少量的文档是可以的。但对于结果非常多的情况,skip 会非常慢,因为需要先找到被略过的结果,然后再丢弃这些数据。大多数数据库会在索引中保存更多的元数据以处理 skip,但 MongoDB 目前还不支持这样做,所以应该避免略过大量的数据
1.不使用skip对结果进行分页
最简单的方式就是使用limit和skip互相配合,用偏移量来进行返回:

> // 不要这么做:略过大量数据会非常慢
> var page1 = db.foo.find(criteria).limit(100)
> var page2 = db.foo.find(criteria).skip(100).limit(100)
> var page3 = db.foo.find(criteria).skip(200).limit(100)
...

使用skip查询很多结果时,会非常慢,所以我们有一种不使用skip来进行分页的方法,就是使用排序和条件查询
假设要按照date**降序显式文档,**可以使用以下方式获取第一页:

> var page1 = db.foo.find().sort({"date" : -1}).limit(100)

然后,假设日期是唯一的,可以使用最后一个文档的 “date” 值作为获取下一页的查询条件:

var latest = null;

// 显示第1页
while (page1.hasNext()) {
    latest = page1.next();
    display(latest);
}

// 获取下一页
var page2 = db.foo.find({"date" : {"$lt" : latest.date}});
page2.sort({"date" : -1}).limit(100);

2.查询一个随机文档
我们首先也会翻译使用skip来随机跳过数据来获取

> // 不要这样做
> var total = db.foo.count()
> var random = Math.floor(Math.random()*total)
> db.foo.find().skip(random).limit(1)

以这种方式获取随机元素实际上非常低效:必须先计算总数(如果使用查询条件,这会是一个昂贵的操作),并且略过大量的元素也会非常耗时
我们还有一种比较好的方法,不过需要提前计划,在文档中设置random键

> db.people.insertOne({"name" : "joe", "random" : Math.random()})
> db.people.insertOne({"name" : "john", "random" : Math.random()})
> db.people.insertOne({"name" : "jim", "random" : Math.random()})

这样,我们就可以使用正常的条件查询来查询,而不用使用skip

> var random = Math.random()
> result = db.people.findOne({"random" : {"$gt" : random}})

random 可能会大于集合中的任何 “random” 值,并且不会返回任何结果。可以简单地从另一个方向返回文档以避免这种情况:

> if (result == null) {
...     result = db.people.findOne({"random" : {"$lte" : random}})
... }

如果集合中没有任何文档,那么这种方式会返回 null,这也是合理的。

4.5.3 游标生命周期

在服务器,游标会占用内存和资源,所以我们需要释放游标,有以下几种情况会释放游标

  • 当游标遍历完结果之后,它会清除自身
  • 当游标超出客户端的作用域,驱动程序会向数据库发送一条特殊的消息,让数据库知道它可以“杀死”该游标
  • 用户没有遍历完所有结果而且游标仍在作用域内,如果 10 分钟没有被使用的话,数据库游标也将自动“销毁”。

有时候,我们需要一个游标维持很长时间,这种情况下,我们应该怎么办呢?
许多驱动程序实现了一个称为 immortal 的函数,或者类似的机制,它告诉数据库不要让游标超时。
如果关闭了游标超时,则必须遍历完所有结果或主动将其销毁以确保游标被关闭。否则,它会一直占用数据库的资源,直到服务器重新启动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值