MongDB常用操作

MongDB常用操作

Reference:https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/

在 Mongo Shell 中使用游标

db.collection.find() 方法返回一个游标,要访问文档需要迭代游标。然而在 mongo shell 中如果没有使用 var 关键字分配给返回的游标变量,游标会自动迭代 20 次最多返回 20 条数据。

以下案例演示如何使用游标访问文档以及如何使用游标索引。

手动迭代游标

1.在 mongo shell 中如果你给 find() 返回的游标变量分配了变量,那么该游标不会自动迭代。

1
2
3
4
> var cursor = db.users.find({}) # 给返回的游标分配变量
> cursor # 调用变量将会默认输出 20 条
{ "_id" : ObjectId("5e7396071a125763738cd268"), "name" : "Harry", "age" : 22, "skill" : [ "Java", "Groovy" ] }
{ "_id" : ObjectId("5e7396071a125763738cd269"), "name" : "Lisa", "hobby" : "watch TV" }

2.可以使用游标方法 next() 迭代文档,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# print(tojson()) 方法可替换成 printjson()
> while(cursor.hasNext()){ print(tojson(cursor.next())) }
{
"_id" : ObjectId("5e7396071a125763738cd268"),
"name" : "Harry",
"age" : 22,
"skill" : [
"Java",
"Groovy"
]
}
{
"_id" : ObjectId("5e7396071a125763738cd269"),
"name" : "Lisa",
"hobby" : "watch TV"
}
> cursor # 已经迭代过了游标没有数据可返回了

3.使用 forEach() 迭代所有数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> var cursor = db.users.find()
> cursor.forEach(printjson)
{
"_id" : ObjectId("5e7396071a125763738cd268"),
"name" : "Harry",
"age" : 22,
"skill" : [
"Java",
"Groovy"
]
}
{
"_id" : ObjectId("5e7396071a125763738cd269"),
"name" : "Lisa",
"hobby" : "watch TV"
}

游标索引

在 mongo shell 中,可以使用 toArray() 返回数据中的文档并迭代游标。

1
2
3
4
5
6
> var cursor = db.users.find() # 返回游标
> var ducomentArray = cursor.toArray() # 加载进数组
> var elements = documentArray[0] # 返回第一个元素
> elements # 访问第一个元素
> var elements = documentArray[1] # 返回第二个元素
> elements # 访问第二个元素

注意:toArray() 会把所有文档加载进内存中,并且会耗光游标。一种快捷方式是直接访问游标上的索引,而不用先转换成数组:

1
2
3
> var cursor = db.users.find()
> var element = cursor[0]
> element

游标行为

关闭不活动的游标

默认情况下游标会在 10 分钟内未活动被服务器自动关闭,或者客户端消耗尽了游标。要在 mongo shell 中覆盖这个行为,可以使用 cursor.noCursorTimeout()

1
var myCursor = db.users.find().noCursorTimeout();

注意:添加了 noCursorTimeout 参数之后,你需要手动关闭游标或者消耗尽游标。

游标隔离性

当你操作游标时,其他操作会和查询一同工作。

游标批次

mongoDB 按批次查询结果,批次处理的数据量不会超过最大 BSON 文档大小。要覆盖批次的大小,使用 batchSize()limit()

3.4之后,find(),aggregate(),listIndexes,listCollections操作每个批次最大返回字节数为 16MB。batchSize()可以限制更小,但不能限制更大。

find() 和 aggregate() 操作默认情况下每个批次操作 101 个文档大小,后续的 getMore() 操作不受这个影响,但受最大 16 MB字节限制。

对于没有加索引的查询排序,服务器在返回数据之前会把文档全部加载进内存做排序。

游标信息

db.serverStatus() 方法将返回包含「监控」字段的文档,该监控字段包含监控的游标如下信息:

  • 上次服务器重启以来超时游标的个数
  • 没有时间限制的游标的打开个数
  • 固定的游标个数
  • 总游标打开的个数

查看游标信息:

1
2
3
4
5
6
7
8
9
> db.serverStatus().metrics.cursor
{
"timedOut" : NumberLong(0),
"open" : {
"noTimeout" : NumberLong(0),
"pinned" : NumberLong(0),
"total" : NumberLong(0)
}
}

batchSize() 和 limit()

batchSize()

指定从 MongoDB 实例响应中,每批返回的文档数量,大多数情况下修改批次大小不会影响用户或应用程序。方法的参数如下:

  • size:每批返回的文档数,不要设置批次大小为 1。

要指定 1 或者类似于负数请使用 limit() 方法

以下实例为查询结果设置批次大小为 2。batchSize() 方法不会改变 mongo shell 的输出,默认情况下迭代器最多返回前 20 个文档。

1
2
3
4
5
6
7
8
9
> db.users.find().size() # 查询总数量
6
> db.users.find().batchSize(2) # 设置批次为2
{ "_id" : ObjectId("5e731375fd228d7458bdc140"), "name" : "Jessica", "age" : 23, "skill" : "game", "hobby" : "sss", "newKey" : "newValue" }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc153"), "name" : "user1", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc154"), "name" : "user2", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc155"), "name" : "user3", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc156"), "name" : "user4", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc157"), "name" : "user5", "age" : 23 }

从输出可以看出,虽然我们设置了每个批次的文档数量为 2,但是全部查询出来了。观察控制台输出可以发现是每次输出两个,不停的迭代,直至遍历完毕或者输出了 20 条。然而我意想的结果是每次只输出 2 条,这显然不是我想要的效果。

limit()

在游标上使用 limit() 方法用于指定游标将要返回的最大文档数量,limit() 类似于 SQL 数据库中的 LIMIT 关键字。

从数据库检索文档之前,必须对游标应用 limit()

使用 limit() 以最大化性能,并防止 MongoDB 返回比处理所需要的更多结果。

db.collection.find(<query>).limit(<number>)

行为

limit 的值如果为 0 等同于没有限制。

负 limit 和正 limit (效果)类似,但会在返回单批数据之后关闭游标。例如:负数限制时,如果有限的结果集不能放入一个批次中,则收到的文档数将小于指定的限制。通过传递负限制,客户端通知服务端它不会通过 getMore() 请求下一个批次的数据。

怎么理解?batchSize() 默认是 101,但是如果我们限制每个批次只返回 2 个,limit(-3) 限制游标每次返回 3 条文档,那么游标返回的结果集不能放入一个批次中,我们就只能收到两条数据。

1
2
3
4
5
6
7
8
9
10
11
> db.users.find() # 一共有 6 条数据
{ "_id" : ObjectId("5e731375fd228d7458bdc140"), "name" : "Jessica", "age" : 23, "skill" : "game", "hobby" : "sss", "newKey" : "newValue" }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc153"), "name" : "user1", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc154"), "name" : "user2", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc155"), "name" : "user3", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc156"), "name" : "user4", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc157"), "name" : "user5", "age" : 23 }
> db.users.find().batchSize(2).limit(-3) # 限定每个批次数量 2 个,限定游标返回的最大文档数 3。
{ "_id" : ObjectId("5e731375fd228d7458bdc140"), "name" : "Jessica", "age" : 23, "skill" : "game", "hobby" : "sss", "newKey" : "newValue" }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc153"), "name" : "user1", "age" : 23 }
>

limit(3) 为正数时,一个批次返回的数据不够,会继续获得下一个批次,直至达到游标总数量限制。

1
2
3
4
> db.users.find().batchSize(2).limit(3)
{ "_id" : ObjectId("5e731375fd228d7458bdc140"), "name" : "Jessica", "age" : 23, "skill" : "game", "hobby" : "sss", "newKey" : "newValue" }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc153"), "name" : "user1", "age" : 23 }
{ "_id" : ObjectId("5e7435b0fd228d7458bdc154"), "name" : "user2", "age" : 23 }

区别

batchSize() 控制单个网络请求中,MongoDB 服务器返回给客户端的文档数量;limit() 控制游标的结果集数量。

batchSize() 是服务端每次返回给客户端的文档数量,limit() 是客户端游标处理的总数据量大小。

  1. limit < batchSize 时,客户端游标遍历完毕不会发送下一个数据请求到服务端
  2. limit > batchSize 且 limit 为正数时,单个批次数据遍历完毕,客户端会发送 getMore 指令获取剩余的 limit 数。
  3. limit > batchSize 且 limit 为负数时,单个批次数据遍历完毕,客户端不会发送指令获取剩余数据。

在 Mongo 中使用实现 like 操作

以下案例中,以 beginTime 降序查询接口日志中 clsId 以 doc 开头、beginTime 为 2020-03-27、bizData 包含 4D260878FCBC7350CEFC9A8357798D65 字符串的所有文档:

1
2
3
db.getCollection('INTERFACE_LOG')
.find({clsId: {$regex: /^doc/i}, beginTime: {$regex: /^2020-03-27/}, bizData: {$regex: /4D260878FCBC7350CEFC9A8357798D65/i}}, {_id: 0})
.sort({beginTime: -1})

Comments