专栏首页MongoDB中文社区有坑勿踩(二): 关于游标

有坑勿踩(二): 关于游标

前 言

聊一聊一个最基本的问题,游标的使用。可能你从来没有注意过它,但其实它在MongoDB的使用中是普遍存在的,也存在一些常见的坑需要引起我们的注意。

在写这个系列文章时,我会假设读者已经对MongoDB有了最基础的了解,因此一些基本名词和概念就不做过多的解释,请自己查阅相关资料。

使用场景

可能你以为你并没有经常在使用游标,但是其实只要在做查询,几乎时时刻刻都在用它。本质上所有查询的数据都是从游标来的。你说你用toArray()?不存在的,它也是在遍历游标然后返回给你一个数组而已。正是因为这样,就出现了第一个问题:除非你确定返回数据量有限,否则不要随便toArray()。

这里说的toArray()包括:

  • shell中的toArray()。 例如: var result = db.coll.find().toArray();
  • node中的toArray()。 例如:var result = await db.collection("coll").find().toArray();
  • python中的list()。 例如:result = list(db.coll.find());
  • Java中的toArray()。 例如:DBCursor.toArray();

因为无论游标里有多少数据,toArray()都会给你挖出来放到内存里,变成数组返回给你。慢不说,内存也占用了很多。所以在可能的情况下,还是尽可能使用hasNext()/next()来得更好。

游标主要来自两个地方:

  • find
  • aggregation

注意二者返回的虽然都是“游标”,但又是两种不同的游标,使用上API也不完全相同,使用的时候请先查阅API(特别是使用NodeJS之类的动态语言的时候不要想当然)。

batchSize与getmore

说完从哪里来,下面就该说说怎么用的问题。

可能你已经从什么地方看到过getmore,比如mongostat的结果中。getmore的作用是从游标中提取一批数据,具体提取多少则是由batchSize决定。

所以当程序进行查询的时候,实际上在后台发生的事情包括:

  1. 驱动在后台获取batchSize条数据并自己缓存起来;
  2. 每次程序调用游标的next()方法时,从这些缓存中提取一条并返回;
  3. 当batchSize条数据都返回完之后,驱动再次通过getmore获取batchSize条数据。

我们可以通过shell来观察这一过程:

  • 先插入一批数据:
  • 强制日志记录所有操作:
  • 跟踪日志:

现在执行一条find语句:

虽然我们在shell中只输出了20条结果,但实际上我们已经从这个游标中获取了50条数据(日志中的黑体部分)。所以当我们继续遍历这个游标时是暂时不需要再次从数据库中取数据的。同时注意我们已经有了一个游标cursor:77199395767。

但当我们第三次遍历20条数据时,则会出现getmore日志:

它通过同一个游标再次提取了50条数据供使用。当我们用完缓存中的数据之前都是不会再看到新的getmore指令的。

游标超时

上面已经了解了游标与驱动是如何配合工作的,那么游标超时是怎么发生的呢?条件很简单,2次getmore之间间隔了超过10分钟,即一个游标在服务端超过10分钟无人访问,则会被回收掉。这时候如果你再针对这个游标进行getmore,就会得到游标不存在的错误(是的,超时的游标在数据库中是不存在的,你得到的错误不会是超时,而是游标不存在。为了便于理解,我们下面还是称之为“游标超时”)。

那么假设你通过游标读取数据的时候是为了进行一系列分析处理,那么下一次getmore在什么时候发生将取决于你的应用在多长时间内消耗完了当前缓存中的数据。换句话说,你的应用处理得越慢,下一次getmore发生的时间就越晚。很多驱动中batchSize的默认值是1000,这也代表着你的应用必须至少能够在10分钟内处理1000条数据,否则就会得到游标超时错误。所以诸如每一条数据需要查询其他数据库1次,需要通过RESTful API到互联网上获取相关的数据,或者需要进行一系列复杂的运算,这样的场景下,问题的关键其实不在于MongoDB怎么样,而在于你的应用到底能够处理多快。

假设问题还是发生了,你的应用遇到了游标超时错误,怎么办呢?你至少可以有以下一些选择:

  1. 延长游标超时时间,请参考cursorTimeoutMillis
  2. 加速应用的处理速度,处理得快了,下一次getmore自然就发生得更早;
  3. 不是那么直观,但是减小batchSize也可以达到同样的目的;
  4. 禁用超时时间(noCursorTimeout)——绝对不推荐使用。虽然可以达到目的,你也可以说我会在最后主动关闭游标的,但事实上总会发生这样那样的意外,导致你最终没有正确关闭游标,最后服务器上塞满了游标的情况也是很常见的。

例外情况

上面已经解释过,在游标超时的时候你得到的实际是“游标不存在”错误,而不是超时。那么反过来是不是也成立呢,“游标不存在”一定是超时了吗?离散数学告诉我们,一个命题的逆命题不一定成立。事实上也是如此。“游标不存在”的另一种可能性是有些用户热衷于在MongoDB前面加上负载均衡/自动故障恢复的软/硬件。我们已经知道游标是存在于一台服务器上的,如果你的负载均衡毫无原则地将请求转发到任意服务器上,getmore同时会因为找不到游标而出现“游标不存在”的错误。

事实上MongoDB和其驱动本身就已经能够完成高可用和负载均衡,并不需要额外画蛇添足。

作者:张耀星

  • MongoDB大中华区高级咨询顾问
  • MongoDB中文社区联席主席

本文分享自微信公众号 - Mongoing中文社区(mongoing-mongoing)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 语言满意度调查报告:Python在走快车道,Java仍让你又爱又恨

    在ActualStand关于开发者对编程语言的满意度的报告里面显示:Python的用户满意度占据领先地位,GO和JavaScript跳跃性增长,而Java却有略...

    三哥
  • 一线城市互联网行业的程序员中,月薪30k算低收入吗?

    作为一个北漂十几年的程序员,三年前离开北京的时候,月薪30k这个数目对于程序员来讲还算是不错的收益,而且即使放在今天一个程序员想随随便便就能拿到30k的月薪也不...

    程序员互动联盟
  • 非常详尽的 Shiro 架构解析!

    Apache Shiro是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。

    Java技术栈
  • Java 10 var关键字详解和示例教程【面试+工作】

    在本文中,我将通过示例介绍新的Java SE 10特性——“var”类型。你将学习如何在代码中正确使用它,以及在什么情况下不能使用它。

    奋斗蒙
  • 动画+原理+代码,解读十大经典排序算法

    排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见...

    昱良
  • 一文理解Netty模型架构

    本文基于Netty4.1展开介绍相关理论模型,使用场景,基本组件、整体架构,知其然且知其所以然,希望给读者提供学习实践参考。

    java思维导图
  • JVM 方法内联

    调用某个函数实际上将程序执行顺序转移到该函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。

    lyb-geek
  • 为什么 Python 这么慢?

    对于一个类似的程序,Python 要比其它语言慢 2 到 10 倍不等,这其中的原因是什么?又有没有改善的方法呢?

    sergiojune
  • 通过javap命令分析java汇编指令

    javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常...

    lyb-geek
  • Dubbo 源码分析 - 服务引用

    在上一篇文章中,我详细的分析了服务导出的原理。本篇文章我们趁热打铁,继续分析服务引用的原理。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服...

    田小波

扫码关注云+社区

领取腾讯云代金券