MongoDB游标

2023-11-17

数据库会使用游标返回 find 的执行结果。游标的客户端实现通常能够在很大程度上对查询的最终输出进行控制。你可以限制结果的数量,跳过一些结果,按任意方向的任意键组合对结果进行排序,以及执行许多其他功能强大的操作。

要使用 shell 创建游标,首先要将一些文档放入集合中,对它们执行查询,然后将结果分配给一个局部变量(用 “var” 定义的变量就是局部变量)。在这里,先创建一个非常简单的集合并对其进行查询,然后将结果存储在 cursor 变量中:

> for (i = 0; i < 100; i++) {
... db.collection.insertOne({x: i})
... }
{
        "acknowledged" : true,
        "insertedId" : ObjectId("6359f2eb20adf7c001484f21")
}
> var cursor = db.collection.find()

这样做的好处是可以一次查看一个结果。如果将结果存储到全局变量中或根本不存储到变量中,那么 MongoDB shell 将自动遍历并显示最开始的几个文档。这是到目前为止我们一直看到的种种例子,通常大家也只是希望看到集合中的内容,而不是使用 shell 进行编程。

要遍历结果,可以在游标上使用 next 方法。可以使用 hasNext 检查是否还有其他结果。典型的结果遍历如下所示:

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

cursor.hasNext() 会检查是否有后续结果存在,而 cursor.next() 用来对其进行获取。

cursor 类还实现了 JavaScript 的迭代器接口,因此可以在 forEach 循环中使用:

> var cursor = db.users.find()
> cursor.forEach(function (x) { print(x.name) })
zhangsan
lisi

调用 find 时,shell 并不会立即查询数据库,而是等到真正开始请求结果时才发送查询,这样可以在执行之前给查询附加额外的选项。cursor 对象的大多数方法会返回游标本身,这样就可以按照任意顺序将选项链接起来了。例如,以下这些是等价的:

> var cursor = db.foo.find().sort({x: 1}).limit(1).skip(10)
> var cursor = db.foo.find().limit(1).sort({x: 1}).skip(10)
> var cursor = db.foo.find().skip(10).limit(1).sort({x: 1})

这时,查询还没有真正执行。所有这些函数只会构造查询。现在,假设执行以下调用:

> cursor.hasNext()

这时,查询会被发往服务器端。shell 会立刻获取前 100 个结果或者前 4MB 的数据(两者之中较小者),这样下次调用 next 或者 hasNext 时就不必再次连接服务器端去获取结果了。在客户端遍历完第一组结果后,shell 会再次连接数据库,使用 getMore 请求更多的结果。getMore 请求包含一个游标的标识符,它会向数据库询问是否还有更多的结果,如果有则返回下一批结果。这个过程会一直持续,直到游标耗尽或者结果被全部返回。

limit、skip和sort

最常用的查询选项是限制返回结果的数量、略过一定数量的结果以及排序。所有这些选项必须在查询被发送到数据库之前指定。

要限制结果数量,可以在 find 之后链式调用 limit 函数。如果只返回 3 个结果,那么可以这样做:

> db.c.find().limit(3)

如果集合中所匹配的文档不到 3 个,则仅返回匹配数量的结果。limit 指定的是上限,而不是下限。

skip 与 limit 类似:

> db.c.find().skip(3)

这个操作会略过前 3 个匹配的文档,然后返回剩下的文档。如果集合中匹配的文档少于 3个,则不会返回任何文档。

sort 会接受一个对象作为参数,这个对象是一组键–值对,键对应文档的键名,值对应排序的方向。排序方向可以是 1(升序)或 -1(降序)。如果指定了多个键,则结果会按照这些键被指定的顺序进行排序。例如,要按照 “username” 升序及 “age” 降序排列,可以这样做:

> db.c.find().sort({"username" : 1, "age" : -1})

这 3 个方法可以结合使用。对于分页来说,这非常方便。假设你正在运营一个在线商店,有人想搜索 mp3。如果想每页返回 50 个结果并按照价格从高到低排序,可以像下面这样做:

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

如果顾客单击了“下一页”以获取更多的结果,可以通过对查询添加 skip 来实现,这样就可以略过前 50 个结果了(这些结果已经显示在第 1 页了):

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

然而,略过大量的结果会导致性能问题。

比较顺序

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

  1. 最小值
  2. null
  3. 数字(整型、长整型、双精度浮点型、小数型)
  4. 字符串
  5. 对象/文档
  6. 数组
  7. 二进制数据
  8. 对象 ID
  9. 布尔型
  10. 日期
  11. 时间戳
  12. 正则表达式
  13. 最大值

避免略过大量结果

使用 skip 来略过少量的文档是可以的。但对于结果非常多的情况,skip 会非常慢,因为需要先找到被略过的结果,然后再丢弃这些数据。大多数数据库会在索引中保存更多的元数据以处理 skip,但 MongoDB 目前还不支持这样做,所以应该避免略过大量的数据。通常下一次查询的条件可以基于上一次查询的结果计算出来。

不使用skip对结果进行分页

最简单的分页方式是使用 limit 返回结果的第 1 页,然后将每个后续页面作为相对于开始的偏移量进行返回:

> // 不要这么做:略过大量数据会非常慢
> 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 来进行分页的方法。假设要按照"date" 降序显示文档。可以通过以下方式来获得结果的第 1 页:

> 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);

这样查询中就没有 skip 了。

查找一个随机文档

从集合中随机选择文档是一个常见的问题。最简单(但很慢)的解决方案是先计算文档总数,然后略过 0 和集合大小之间的一个随机的文档数量来执行一次 find 查询:

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

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

这需要一些提前规划,但如果你知道要在集合中查找随机元素,那么有一种高效得多的方法可以执行此操作。诀窍就是在插入文档时为每个文档添加一个额外的随机键。如果正在使用 shell,那么可以使用 Math.random() 函数(这会产生 0 和 1 之间的一个随机数):

> 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.users.findOne({"random" : {"$lte" : random}})
... }

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

这种方式可以用于任意复杂的查询,只需确保有一个包含随机键的索引。如果要在加利福尼亚州随机找一个水管工,那么可以在 “profession”、“state” 和 “random” 上创建一个索引:

> db.users.ensureIndex({"profession" : 1, "state" : 1, "random" : 1})

这便可以快速地找到一个随机结果。

游标生命周期

游标包括两个部分:面向客户端的游标和由客户端游标所表示的数据库游标。

在服务器端,游标会占用内存和资源。一旦游标遍历完结果之后,或者客户端发送一条消息要求终止,数据库就可以释放它正在使用的资源。释放这些资源可以让数据库将其用于其他用途,这是非常有益的,因此要确保可以尽快(在合理的范围内)释放游标。

还有一些情况可能导致游标终止以及随后的清理。首先,当游标遍历完匹配的结果时,它会清除自身。其次,当游标超出客户端的作用域时,驱动程序会向数据库发送一条特殊的消息,让数据库知道它可以“杀死”该游标。最后,即使用户没有遍历完所有结果而且游标仍在作用域内,如果 10 分钟没有被使用的话,数据库游标也将自动“销毁”。这样,如果客户端崩溃或者出错,MongoDB 就不需要维护上千个被打开的游标了。

这种“超时销毁”的机制通常是用户所期望的:很少有用户愿意花几分钟坐在那里等待结果。然而,有时可能的确需要一个游标维持很长时间。在这种情况下,许多驱动程序实现了一个称为 immortal 的函数,或者类似的机制,它告诉数据库不要让游标超时。如果关闭了游标超时,则必须遍历完所有结果或主动将其销毁以确保游标被关闭。否则,它会一直占用数据库的资源,直到服务器重新启动。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MongoDB游标 的相关文章

随机推荐

  • 【ChatGPT】基于WSL+Docker的ChatGPT PLUS共享服务部署

    最近买了ChatGPT PLUS服务 想通过web服务将它共享给其他人使用 搜了一下目前GitHub上比较热门的服务有 ChatGPT Next Web chatgpt web share 其中chatgpt web share支持API和
  • 【uni-app】css 关于 calc()函数计算无效

    计算符 注 计算 符前后都需要空格 否则计算无效
  • 华为OD机试真题 Java 实现【最多提取子串数目】【2023Q1 100分】

    一 题目描述 给定由 a z 26 个英文小写字母组成的字符串 A和 B 其中A中可能存在重复字母 B 中不会存在重复字母 现从字符串 A 中按规则挑选一些字母 可以组成字符串 B 挑选规则如下 同一个位置的字母只能被挑选一次 被挑选字母的
  • ue4加载本地版本_【虚幻4】创建本地数据库

    简介 这里我们主要通过使用Data table实现本地数据库 Data table可以用来保存一些用户配置 或者常用变量 或者用来实时更新外部表格数据到虚幻4中 一 创建Data table 1 首先创建Structure结构 这里我已经创
  • 我用什么写Python?

    通常来说 每个程序员都有自己趁手的兵器 代码编辑器 你要是让他换个开发环境 恐怕开发效率至少下降三成 然而 每个人对编辑器的喜好各不相同 甚至引发出诸如 神的编辑器 与 编辑器之神 这种信仰之争 但也正由此可见 个性化的编辑器对于一个程序员
  • 【FICO系列】SAP FICO 凭证错误:BKPFF$PRDCLN800在FI中达到的项目最大编号

    公众号 SAP Technical 本文作者 matinal 原文出处 http www cnblogs com SAPmatinal 原文链接 FICO系列 SAP FICO 凭证错误 BKPFF PRDCLN800在FI中达到的项目最大
  • 无法访问目标主机的原因及其和请求超时的区别

    使用ping命令时经常会遇到这两种情况 就表示网络出了问题 无法访问目标主机的原因 可以看到 无法访问目标主机 是来自一个IP的回复 实际上那个IP是一个路由器 因此 无法访问目标主机 实际上数据是发出去并且收到回复的 只不过收到的回复是别
  • 数据结构和算法(递归概念、迷宫回溯问题和八皇后问题代码实现)

    递归的概念 递归能够做解决什么问题 使用递归时需要注意的问题 递归的第一个应用 迷宫回溯问题 迷宫模拟 定义一个8 7的数组模拟迷宫 1表示围墙 0表示可以走的路 图中左上红圈为起点 右下红圈为终点 利用代码找到从起点到终点的路径 使用递归
  • 【Python】代码实现LL(1),LR(1)上下文无关文法(Stack()类)

    任务要求 针对书上第三章中的表达式文法 采用LL 1 LR 1 进行分析 相关文法 需要进行消除左递归等操作 顺手分享一下课本资源好了 可能不是最新版 排版略有点别扭 后文的书上内容就是指这本书 编译原理 陈意云 文字版 提取码 e0ag
  • Android Studio如何添加工程(project)为library(针对非gradle)

    这篇文章还是针对非gradle build的工程 gradle build有一些差别 在Eclipse要引用别的工程为本工程的library很简单 但是在Android Studio还是稍稍有点小复杂的 那如何引用别的工程为本工程的libr
  • 网络编程——TCP并发服务器模型

    1 多线程中的newfd 能否修改成全局 不行 为什么 因为如果是全局变量 文件描述符就是唯一的 所有的客户端都会在同一个文件描述符通信 2 多线程中分支线程的newfd能否不另存 直接用指针间接访问主线程中的newfd 不行 为什么 如果
  • 微信小程序-仿智行火车票12306

    微信小程序 仿智行火车票12306 微信小程序 仿智行火车票12306 主页有轮播图 有导航栏 有个人中心 可以实现火车票 飞机票 汽车票的选择 适合初学者学习 下面是示例图片 下载链接 https download csdn net do
  • Linux系统图形界面和命令行界面之间的切换

    一 系统不在虚拟机中的情况 使用ctrl alt F1 6切换到命令行界面 ctrl alt F7切换到图形界面 二 系统在虚拟机中的情况 Ctrl Alt shift F1 6切换到命令行界面 使用Alt F7返回到图形界面 注 以上方法
  • hashmap中为什么使用红黑树?

    在回答这个问题之前 我们先了解一下有关二叉树的基本内容 二叉排序树 又称二叉查找树 1 若左子树不为空 则左子树上所有结点的值均小于根结点的值 2 若右子树不为空 则右子树上所有结点的值均大于根节点的值 3 左右子树也为二叉排序树 平衡二叉
  • 2017 ICCV之语义分割:Cascaded Feature Network for Semantic Segmentation of RGB-D Images

    Cascaded Feature Network for Semantic Segmentation of RGB D Images 目前的问题 1 为了计算对象 场景关系的表示 最近大量的分割网络使用一组感受野来丰富卷积特征的文本信息 这
  • book_read_link

    结构性改革 黄奇帆 微信读书 分析与思考 黄奇帆的复旦经济课 黄奇帆 微信读书
  • java通过web3j获取ETH交易明细

    我们在项目里面如果想要得到用户的ETH交易明细怎么做呢 有两种方式 1 直接获取ETH最新块的交易明细 2 通过块获取用户的交易明细 废话不多说 直接贴代码看了 package com example demo web3jLog impor
  • pdf.js引入方式及初始化配置

    官方下载地址 Getting StartedA general purpose web standards based platform for parsing and rendering PDFs http mozilla github
  • actuator--基础--04--Springboot集成

    actuator 基础 04 Springboot集成 代码位置 https gitee com DanShenGuiZu learnDemo tree master actuator learn actuator01 1 代码 1 1 依
  • MongoDB游标

    数据库会使用游标返回 find 的执行结果 游标的客户端实现通常能够在很大程度上对查询的最终输出进行控制 你可以限制结果的数量 跳过一些结果 按任意方向的任意键组合对结果进行排序 以及执行许多其他功能强大的操作 要使用 shell 创建游标