游标一批多少个?batchSize 设小了会怎样?10 分钟超时到底怎么回事?遍历时别人改数据会读到什么?这篇把这些逐个验证。
共 3 条命令(1 个 find + 2 个 getMore);第一批 101 个;之后每批: [70538, 29361] …;最后一批 29361 个 总共读到 100,000 个文档
第一批 101 个文档,之后每个 getMore 装满 16 MiB 为止;10 万个文档只要 1 个 find 加 2 个 getMore。
batch_size 命令数 耗时
10 10001 944 ms
100 1001 140 ms
1000 101 62 ms
10000 11 54 ms
默认 3 55 ms批次太小增加往返:10 要 10,001 条命令(944 ms),默认 3 条命令(55 ms)。
# 读之前
test> db.serverStatus().metrics.cursor.open
{
multiTarget: Long('0'),
noTimeout: Long('0'),
pinned: Long('0'),
queuedData: Long('0'),
singleTarget: Long('0'),
total: Long('0')
}
# 开了一个游标、只读了第一条之后
test> db.serverStatus().metrics.cursor.open
{
multiTarget: Long('0'),
noTimeout: Long('0'),
pinned: Long('0'),
queuedData: Long('0'),
singleTarget: Long('0'),
total: Long('1')
}
# cur.close() 之后
test> db.serverStatus().metrics.cursor.open
{
multiTarget: Long('0'),
noTimeout: Long('0'),
pinned: Long('0'),
queuedData: Long('0'),
singleTarget: Long('0'),
total: Long('0')
}没读完的游标计在 metrics.cursor.open 里,close() 后释放。
# 默认值(毫秒)
test> db.adminCommand({getParameter: 1, cursorTimeoutMillis: 1}).cursorTimeoutMillis
Long('600000')
test> var r = db.adminCommand({setParameter: 1, cursorTimeoutMillis: 3000}); r.was
Long('600000')
不带 lsid 的 find(batchSize 10)开了 5 个游标;每个游标只在指定的空闲时间点做一次 getMore(之前不碰它,因为每次 getMore 都会重置空闲计时):
空闲 2.0 s 后 getMore:成功(返回 10 个)
空闲 5.0 s 后 getMore:失败 code=43(CursorNotFound): cursor id 349517662137719466 not found
空闲 8.0 s 后 getMore:失败 code=43(CursorNotFound): cursor id 6889896205663917514 not found
空闲 12.0 s 后 getMore:失败 code=43(CursorNotFound): cursor id 8350820545976234763 not found
空闲 20.0 s 后 getMore:失败 code=43(CursorNotFound): cursor id 8106019490526629753 not found
对照:pymongo 的游标(隐式会话)空闲 15 秒后(超过 3 秒的游标超时)仍然可用:可以继续读(游标绑定在会话上,由会话超时管理,默认 30 分钟)不绑定会话的游标空闲超过 cursorTimeoutMillis 后会被后台回收,再 getMore 报 43 CursorNotFound;每次 getMore 都会重置空闲计时。绑定在会话上的游标(驱动默认)由会话超时管理。
no_cursor_timeout 的游标空闲 8 秒(超过 cursorTimeoutMillis 3 秒)后仍可继续读
# serverStatus 里 noTimeout 的游标数
test> var o = db.serverStatus().metrics.cursor.open; ({noTimeout: o.noTimeout, total: o.total})
{ noTimeout: Long('1'), total: Long('1') }
test> db.adminCommand({setParameter: 1, cursorTimeoutMillis: 600000}).was
Long('3000')不会被超时回收,但要自己关闭。
按 k 排序遍历 20,000 个文档时,另一线程把已经读过的文档的 k 改大:共读到 21,500 个,其中同一个 _id 重复出现 1,500 次(不同 _id 20,000 个)
遍历期间的修改游标看得到:被改到后面的文档会再读一次,遍历结果可能重复或遗漏。