可追加游标迭代
概述
当驱动程序执行查询或命令(例如 aggregate )时,操作结果将通过 MongoDB\Driver\Cursor 对象。 Cursor 类实现 PHP 的 迭代器 接口,允许使用 进行迭代,并与任何使用foreach
可迭代对象 的 PHP 函数连接 。与其他数据库驱动程序中的结果对象类似,MongoDB 中的游标仅支持前向迭代,这意味着它们不能回滚或与foreach
一起使用多次。
可追加游标是一种特殊类型的 MongoDB 游标,允许客户端读取一些结果,然后等待,直到有更多文档可用。这些游标主要与固定大小集合和变更流一起使用。
虽然普通游标可以使用foreach
迭代一次,但该方法不适用于可追加游标。当foreach
与可追加游标一起使用时,循环将在到达初始结果集的末尾时停止。尝试使用第二个foreach
继续在游标上迭代会引发异常,因为 PHP 会尝试倒回游标。因此,从可追加游标读取数据需要直接使用 迭代器 API。
注意
190之前的版本。ext-mongodb
。 扩展的 ,游标类未实现 迭代器 接口。要使用以下方法手动迭代游标,必须首先用 IteratorIterator 包装游标。
手动遍历普通游标
在了解如何迭代可追加游标之前,我们将首先研究Iterator
方法如何与普通游标交互。
以下示例找到五家餐厅并使用foreach
查看结果:
$collection = (new MongoDB\Client)->test->restaurants; $cursor = $collection->find([], ['limit' => 5]); foreach ($cursor as $document) { var_dump($document); }
虽然这个示例非常简洁,但实际上发生了很多事情。foreach
构造首先会倒回迭代器(在本例中为$cursor
)。然后检查当前位置是否有效。如果该位置无效,则循环结束。否则,将相应地访问当前键和值并执行循环体。假设 休息 ,然后迭代器前进到下一个位置,控制跳回有效性检查,然后继续循环。
掌握了foreach
的内部运作方式后,我们现在可以将前面的示例转换为直接使用迭代器方法:
$collection = (new MongoDB\Client)->test->restaurants; $cursor = $collection->find([], ['limit' => 5]); $cursor->rewind(); while ($cursor->valid()) { $document = $cursor->current(); var_dump($document); $cursor->next(); }
注意
在while
循环自然结束后调用$cursor->next()
会引发异常,因为游标上的所有结果均已用尽。
此示例的目的是演示foreach
和使用 PHP 迭代器 进行手动迭代之间的功能等价 API。对于普通游标,没有理由手动迭代结果而不是简洁的foreach
循环。
迭代可追加游标
为了演示实际操作中的可追加游标,我们需要两个脚本:“生产者”和“消费者”。 生产者脚本将使用MongoDB\Database::createCollection()
创建一个新的固定大小集合,并继续每秒将一个新文档插入到该集合中。
$database = (new MongoDB\Client)->test; $database->createCollection('capped', [ 'capped' => true, 'size' => 16777216, ]); $collection = $database->selectCollection('capped'); while (true) { $collection->insertOne(['createdAt' => new MongoDB\BSON\UTCDateTime()]); sleep(1); }
在生产者脚本仍在运行的情况下,我们现在将执行使用者脚本,以使用可追加游标读取插入的文档,如MongoDB\Collection::find()
的cursorType
选项所示。我们将首先使用foreach
来说明其缺点:
$collection = (new MongoDB\Client)->test->capped; $cursor = $collection->find([], [ 'cursorType' => MongoDB\Operation\Find::TAILABLE_AWAIT, 'maxAwaitTimeMS' => 100, ]); foreach ($cursor as $document) { printf("Consumed document created at: %s\n", $document->createdAt); }
如果执行此使用者脚本,您会注意到它很快耗尽固定大小集合中的所有结果,然后终止。我们无法添加第二个foreach
,因为这会在尝试倒带游标时引发异常。这是使用 迭代器 直接控制迭代过程的成熟用例 接口。
$collection = (new MongoDB\Client)->test->capped; $cursor = $collection->find([], [ 'cursorType' => MongoDB\Operation\Find::TAILABLE_AWAIT, 'maxAwaitTimeMS' => 100, ]); $cursor->rewind(); while (true) { if ($cursor->valid()) { $document = $cursor->current(); printf("Consumed document created at: %s\n", $document->createdAt); } $cursor->next(); }
与foreach
示例非常相似,此版本的使用者脚本将首先快速打印固定大小集合中的所有结果;但是,它不会在到达初始结果集的末尾时终止。 由于我们使用的是可追加游标,因此调用next()
会阻塞并等待其他结果,而不是引发异常。 我们还将使用valid()
在每一步检查是否确实有数据可供读取。
由于我们选择使用TAILABLE_AWAIT
游标,因此服务器将对驱动程序的响应延迟设定的时间。 在此示例中,我们通过将maxAwaitTimeMS
选项指定为MongoDB\Collection::find()
来请求服务器阻塞大约 100 毫秒。