MongoDB 中的光标是什么?

What is a Cursor in MongoDB?

我们对某些 Morphia 查询 asList and I've found a hint on SO 最终发生 cursor not found exceptions 感到困扰,这可能会非常消耗内存。

现在我想了解更多关于背景的信息:谁能解释一下(用英语),什么是 Cursor(在 MongoDB 中)?为什么一直打开还是找不到?


文档defines一个游标为:

A pointer to the result set of a query. Clients can iterate through a cursor to retrieve results. By default, cursors timeout after 10 minutes of inactivity

但这不是很能说明问题。为查询结果定义 batch 可能会有所帮助,因为 documentation also states:

The MongoDB server returns the query results in batches. Batch size will not exceed the maximum BSON document size. For most queries, the first batch returns 101 documents or just enough documents to exceed 1 megabyte. Subsequent batch size is 4 megabytes. [...] For queries that include a sort operation without an index, the server must load all the documents in memory to perform the sort before returning any results.

注意:在我们的查询中,我们根本不使用排序语句,也没有 limitoffset.

这是 toArray() 和 Node.js MongoDB 驱动程序中 find() 之后的游标之间的比较。常用代码:

var MongoClient = require('mongodb').MongoClient,
assert = require('assert');

MongoClient.connect('mongodb://localhost:27017/crunchbase', function (err, db) {
    assert.equal(err, null);
    console.log('Successfully connected to MongoDB.');

    const query = { category_code: "biotech" };

    // toArray() vs. cursor code goes here
});

这是上一节中的 toArray() 代码。

    db.collection('companies').find(query).toArray(function (err, docs) {
        assert.equal(err, null);
        assert.notEqual(docs.length, 0);

        docs.forEach(doc => {
            console.log(`${doc.name} is a ${doc.category_code} company.`);
        });

        db.close();
    });

根据文档,

The caller is responsible for making sure that there is enough memory to store the results.

这是基于游标的方法,使用 cursor.forEach() 方法:

    const cursor = db.collection('companies').find(query);

    cursor.forEach(
        function (doc) {
            console.log(`${doc.name} is a ${doc.category_code} company.`);
        },
        function (err) {
            assert.equal(err, null);
            return db.close();
        }
    );
});

使用 forEach() 方法,我们不是在内存中获取所有数据,而是将数据流式传输到我们的应用程序。 find() 立即创建一个游标,因为在我们尝试使用它将提供的某些文档之前,它实际上并不向数据库发出请求。 cursor 的要点是描述我们的查询。 cursor.forEach 的第二个参数显示发生错误时要执行的操作。

在上述代码的初始版本中,是 toArray() 强制调用数据库。这意味着我们需要 ALL 个文档并希望它们位于 array 中。

注意MongoDBreturns数据是分批处理的。下图显示了从游标(从应用程序)到 MongoDB:

的请求

forEachtoArray 更好地扩展,因为我们可以处理文档 当它们进来时 直到我们到达终点。将其与 toArray 进行对比 - 我们等待 ALL 检索文档并构建 entire 数组。这意味着我们没有从驱动程序和数据库系统协同工作以将结果批处理到您的应用程序这一事实中获得任何优势。批处理旨在提供内存开销和执行时间方面的效率。 如果可以,请在您的应用程序中利用它。

我绝不是 mongodb 专家,但我只是想补充一些去年在中型 mongo 系统中工作的观察结果。还要感谢@xameeramir 对游标的一般工作原理进行了出色的演练。

"cursor lost" 异常的原因可能有多种。我注意到的一个在这个答案中有解释。

光标位于服务器端。它不分布在副本集上,而是存在于创建时的主要实例上。这意味着如果另一个实例接管为主实例,游标将丢失给客户端。如果旧的主节点还在运行,它可能仍然存在但没有用。我猜它会在一段时间后被垃圾收集掉。因此,如果您的 mongo 副本集不稳定或者您的网络不稳定,那么在执行任何长 运行ning 查询时,您就不走运了。

如果游标想要 return 的全部内容不适合服务器的内存,查询可能会很慢。服务器上的 RAM 需要大于您 运行.

的最大查询

所有这些都可以通过更好的设计来部分避免。对于具有大型长 运行ning 查询的用例,您最好使用几个较小的数据库集合而不是一个大数据库集合。

当您有大量数据并且正在对该数据进行批处理并且每个批处理需要更多时间,总计时间超过默认游标生存时间时,也会出现此错误。

然后您需要更改默认时间以告知 mongo 在处理完成之前此游标不会过期。

检查No TimeOut Documentation

游标是通过调用 db.collection.find() 返回的对象,它可以迭代 [=15] 的文档(NoSQL 相当于 SQL "row") =] 集合(没有 SQL 相当于 "table")。

集合的 find 方法 return 是一个 cursor - 这指向文档集(称为 result set) 与查询过滤器匹配。结果集是查询 return 编辑的实际文档,但它位于数据库服务器上。

给客户端程序,比如mongo shell,你得到一个游标。您可以认为游标就像一个 API 或一个处理结果集的程序。游标有很多方法可以运行对结果集执行一些操作。有些方法会影响结果集数据,有些方法会提供有关结果集的状态或信息。

由于游标维护有关结果集的信息,因此当您通过应用其他游标方法使用结果集数据时,某些信息可能会发生变化。您可以使用这些方法和信息来适应您的应用程序,即您希望如何以及如何处理查询的数据。


使用游标及其一些常用方法和功能处理结果集 mongo shell:

count()方法returns count结果集中的文档数,最初 - 作为查询的结果。它在游标生命周期的任何时候始终保持不变。这是信息。即使在游标关闭或耗尽后,此信息仍保持不变。

当您从结果集中读取文档时,结果集会耗尽。一旦完全筋疲力尽,您就无法再阅读了。 hasNext() 告诉是否有任何文档可供读取 - returns 布尔值 true 或 false。如果可用,next() return 是一个文档(您首先检查 hasNext,然后执行 next)。这两种方法通常用于对结果集数据进行迭代。另一种迭代方法是 forEach().

数据以 批次 从服务器检索 - 具有默认大小。使用第一批读取文档,当读取所有文档时,以下 next() 方法检索下一批文档,依此类推,直到从结果集中读取所有文档。这个批量大小可以配置,你也可以得到它的状态。

如果您在光标上应用 toArray() 方法,则结果集中所有剩余的文档都将加载到您的客户端计算机的内存中,并可作为 JavaScript 数组。并且,结果集数据耗尽。下面的hasNext方法会returnfalsenext会报错(一旦游标耗尽,就不能从游标中读取数据)。此方法将所有结果集数据加载到客户端的内存(数组)中。如果结果集很大,这可能会消耗内存。

结果集中itcount()returncount个剩余文档,耗尽游标.

isClosed()isExhausted()size() 等游标方法,它们提供 status在您处理数据时有关游标及其基础结果集的信息。

这些是游标和结果集的基本特征。游标的方法有很多,你可以试试看它们是如何工作的,以便更好地理解。

参考:


mongoshell中的用法示例:

假设 test 集合有 200 个文档(运行 命令顺序相同)。

  • var cur = db.test.find( { } ).limit(25) 创建一个包含 25 个的结果集 仅文档。
  • 但是,cur.count()会显示200,这是实际的计数 查询过滤器的文档。
  • hasNext() 将 return true.
  • next() 将 return 一个文件。
  • itcount() 将 return 24(并耗尽光标)。
  • itcount() 又会 return 0.
  • cur.count()还是会显示200。

如果您的集群稳定且没有成员出现故障或状态发生变化,则找不到游标的最可能原因是:

默认空闲游标超时为 10 分钟,但在版本 >= 3.6 中,游标还与默认会话超时 30 分钟的会话相关联,因此即使您使用选项 noCursorTimeout() 将游标设置为不过期仍然受到 30 分钟会话超时的限制。为了避免你的光标被会话超时杀死,你需要定期检查你的代码并执行 sessionRefresh 命令:

   db.adminCommand({"refreshSessions" : [sessionId]})

将会话再延长 30 分钟,这样如果您在获取下一批数据之前对数据进行了一些操作,您的光标就不会被杀死... 查看此处的文档以了解如何操作的详细信息:

https://docs.mongodb.com/manual/reference/method/cursor.noCursorTimeout/