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

😁 作者简介:一名大四的学生,致力学习前端开发技术

⭐️个人主页:夜宵饽饽的主页

❔ 系列专栏:MongoDB数据库

👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气

​🔥​前言:

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

文章目录

  • [第4章 查询](#第4章 查询)
    • [4.1 find简介](#4.1 find简介)
      • [4.1.1 指定要返回的键](#4.1.1 指定要返回的键)
    • [4.3 特定类型查询](#4.3 特定类型查询)
      • [4.3.3 查询数组之slice操作符](#4.3.3 查询数组之slice操作符)
      • [4.3.5 查询数组之数组与范围查询的相互作用](#4.3.5 查询数组之数组与范围查询的相互作用)
    • [4.4 where查询](#4.4 where查询)
    • [4.5 游标](#4.5 游标)
      • [4.5.1 limit,skip和sort](#4.5.1 limit,skip和sort)
      • [4.5.2 避免略过大量结果](#4.5.2 避免略过大量结果)
      • [4.5.3 游标生命周期](#4.5.3 游标生命周期)

第4章 查询

4.1 find简介

4.1.1 指定要返回的键

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

案例:

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

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

javascript 复制代码
> db.users.find({}, {"fatal_weakness" : 0})

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

javascript 复制代码
> db.users.find({}, {"username" : 1, "_id" : 0})
{
    "username" : "joe"
}

4.3 特定类型查询

4.3.3 查询数组之$slice操作符

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

例子:

javascript 复制代码
{
    "_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" 键的某一个元素与查询条件的任意一条语句相匹配(查询条件中的每条语句可以匹配不同的数组元素)时,此文档也会被返回。

接下来我们可以来看一个例子:

现在有一个文档

javascript 复制代码
{"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}。然而,当实际运行时,我们得到了两个文档,如下所示:

javascript 复制代码
> 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不会匹配非数组元素

javascript 复制代码
> 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章),那么可以使用min和max将查询条件遍历的索引范围限制为"gt" 和 "$lt" 的值**

javascript 复制代码
> 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 代码

例子:

js 复制代码
> 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 返回一个游标,它指向查询结果的初始位置。通过游标,你可以逐步获取结果集中的文档,而不需要一次性将整个结果集加载到内存中。

案例:

javascript 复制代码
> 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 个结果并按照价格从高到低排序

javascript 复制代码
> 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互相配合,用偏移量来进行返回:

javascript 复制代码
> // 不要这么做:略过大量数据会非常慢
> 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**降序显式文档,**可以使用以下方式获取第一页:

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

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

javascript 复制代码
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来随机跳过数据来获取

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

以这种方式获取随机元素实际上非常低效:必须先计算总数(如果使用查询条件,这会是一个昂贵的操作),并且略过大量的元素也会非常耗时

我们还有一种比较好的方法,不过需要提前计划,在文档中设置random键

javascript 复制代码
> 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

javascript 复制代码
> var random = Math.random()
> result = db.people.findOne({"random" : {"$gt" : random}})

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

javascript 复制代码
> if (result == null) {
...     result = db.people.findOne({"random" : {"$lte" : random}})
... }

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

4.5.3 游标生命周期

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

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

有时候,我们需要一个游标维持很长时间,这种情况下,我们应该怎么办呢?

许多驱动程序实现了一个称为 immortal 的函数,或者类似的机制,它告诉数据库不要让游标超时。

如果关闭了游标超时,则必须遍历完所有结果或主动将其销毁以确保游标被关闭。否则,它会一直占用数据库的资源,直到服务器重新启动

相关推荐
VALENIAN瓦伦尼安教学设备10 分钟前
设备对中不良的危害
数据库·嵌入式硬件·算法
小兔崽子去哪了22 分钟前
Docker 安装 PostgreSQL
数据库·后端·postgresql
野犬寒鸦26 分钟前
Redis热点key问题解析与实战解决方案(附大厂实际方案讲解)
服务器·数据库·redis·后端·缓存·bootstrap
mldlds1 小时前
Windows安装Redis图文教程
数据库·windows·redis
Y001112361 小时前
JDBC原理
java·开发语言·数据库·jdbc
吴声子夜歌1 小时前
TypeScript——模块解析
javascript·ubuntu·typescript
超级大只老咪1 小时前
固定个数的状态,需要按顺序无限循环切换
数据库
han_2 小时前
JavaScript设计模式(五):装饰者模式实现与应用
前端·javascript·设计模式
@insist1232 小时前
数据库系统工程师-云计算与大数据核心知识
大数据·数据库·云计算·软考·数据库系统工程师·软件水平考试
皙然2 小时前
深度解析:关系型数据库与非关系型数据库(区别+原理+适用场景,一文吃透)
数据库·nosql