【MongoDB权威指南】二

二 查询

这里是本系列第二篇,对应书中第三章,主要讲各种find的技巧。

find

返回指定的键

为了节省流量和客户端解读开销等等,只返回指定的键。把需要的键放在第二位参数就好了。

把要的键的值设置为1则返回,设置为0则不返回。

查询条件

大小比较包括

  • $lt 小于
  • $lte 小于等于
  • $gt
  • $gte
  • $ne
1
db.data.find({'age': {'$lt': 24, '$gt': 18}})

or查询

$in

1
db.data.find({'number': {'$in': [10, 20, 30]}})

$nin代表不在这里头。

1
db.data.find({'number': {'$nin': [10, 20, 30]}})

$or

1
db.data.find({'$or': [{'name': 'aa'}, {'age': 18}])

$not

null

会匹配值是null的,以及没有此键的。

若只要匹配值是null的,需要这么写

1
db.data.find({'z': {'$in': [null], '$exsits': true}})

即是存在该字段并且为null。

使用正则表达式搜索

使用/符号包裹正则表达式,不用加引号了。

1
db.data.find({'name': /abo/})

查询数组

如果键是数组名,则只要值是数组中任意一个元素,都可以查询出数组来。

1
2
db.data.insert({'array': [1, 2, 3]})
db.find({'array': 2})

$all

需要拥有所有元素,顺序无关紧要。

1
db.data.find({'array': {'$all': [3,1,2,4]}})

$size

顾名思义查询数组长度。但是不节省性能。
建议自己维护一个size字段,每次插入的时候顺便用{'$inc': {'size': 1}}来更新它。

slice

获取返回数列的一部分,用法有二:

  • {'$slice': [min, max]} 返回查询后数组中的min到max间的结果
  • {'$slice': index} 返回索引为index的元素,当index为-1时返回最后一个

嵌入查询

假设数据如下

1
2
3
4
5
6
7
{
'name':{
'first': 'ABo',
'last': 'Lin'
},
'age': 18
}

第一种查询条件

1
{'name': {'firtst': 'ABO', 'last': 'Lin'}}

这种方法的缺点是必须精确匹配,如果name中又加入一个middle,那么就搜索不出来了。为了可以模糊搜索,我们用嵌入查询。

第二种查询条件

1
{'name': {'elemMatch': {'firtst': 'ABO', 'last': 'Lin'}}}

这种查法是部分匹配即可。

where查询

直接使用一个js函数来查询。函数的第一个参数this为where过滤前的结果集合。

假设需要返回集合里有重复值的集合,可以这么写

1
2
3
4
5
6
7
8
9
10
db.data.find({'$where', function(){
for(var current in this){
for(var other in this){
if current != other and this[current] == this[other]{
return true
}
}
}
return false
}})

不仅可以传入一个function对象,传入字符串也是可以的。比如

1
2
db.data.find({'$where': 'this.x + this.y == 10'}) // 直接写判断语句
db.data.find({'where': 'function() {return this.x + this.y == 10}'})

游标

如果用一个变量来接住find返回的结果,那这个变量就变成了游标cursor。cursor的用法无非就那样

1
2
3
while(cursor.hasNext()){
obj = cursor.next()
}

注意,当产生cursor的时候,并没有马上查询数据库。真正执行查询操作的时候在cursor.hasNext()之时。这样就使得我们可以对cursor执行复杂的链式操作了。譬如:

1
var cursor = db.data.find().sort({'x': 1}).limit(1).skip(10)

当执行第一次hasNext的时候,立即尝试从服务器获取前100个结果,或者4MB的数据中的较小者。当第一批数据被用完了之后,再次hasNext又会取回新的一批数据,如此往复直至游标耗尽。

后续操作

  • limit 限制返回的结果个数
  • skip 跳过前几个结果
  • sort 排序,1是正序,-1是倒序

利用skip做分页

最简单的方法如下

1
2
3
var page1 = db.data.find(condition).limit(100)
var page2 = db.data.find(condition).skip(100).limit(100)
var page3 = db.data.find(condition).skip(200).limit(100)

但是其实大剂量的skip是非常慢的,所以需要避免。

可以用一个trick来避免分页。

  1. 对整个文档做一次降序排序
  2. 取前n个作为第一页,然后取其中最小者作为the_last
  3. 再次find,这次条件为要比the_last更小
  4. 再对3产生的cursor做一次降序排序,并limit设置n作为第二页

有个疑问
对所有文档做排序就不耗性能了么?
不过想了想由于cursor是分批次取数据的,所以这个排序相当于经典的「求最大的k个数」题目了。
顺便看这个网页复习了下。可以利用堆排序找出最大k个数。

随机选取文档

当然最常见的方法就是弄个随机数来skip(nRandom)然后limit(1)。但是计算总文档数目和skip这两个操作就是比较耗时的。

比较投机取巧的方法是每次插入文档的时候都带一个随机数。当需要随机的时候只要产生一个随机数,并作为条件去筛选文档即可。(卧槽这都可以?)偶尔会遇到往一个方向(比如大于我random出来的随机数)找不到,那就反个方向。

链式查询转换

实际上,执行db.data.find(condition).sort(sortDict)的时候,是把条件转化成了db.data.find({'$query': condition, '$orderby': sortDict})。除了常用的$query$orderby以外,还有一些有用的高级选项比如:

  • $maxscan 查询最多扫描的文档数量
  • $min 查询开始条件
  • $max
  • $hint 使用哪个索引进行查询
  • $explain 不是真正的做查询,只是为了获得耗时、结果数量等信息
  • $snapshot 确保查询的是执行查询那一刻的时候的快照

保证结果一致性

假设我们需要对每个文档进行修改,并且是新增加内容。

1
2
3
4
5
6
cursor = db.data.find(condition)
while(cursor.hasNext()){
var document = cursor.next()
modify(document)
db.data.save(document)
}

由于数据库对每个文档预留的大小有限,当文档变大之后,可能它就无法再塞入到原本那个位置了,数据库会把它放到最后边去。这样造成的结果是,cursor遍历到的有可能是已经前面modify过的文档。

$snapshot就解决了这个问题。它在开始查询那一瞬间就保留了文档快照,这使得后面文档的位置不管怎么挪,都不再会影响查询结果。

游标的存在时间

游标在几种情况下会销毁并释放资源

  • 遍历完成之后
  • 游标超出客户端作用域,驱动向服务器发送专门的消息(估计这得要求语言有像c++一样超出作用域之后执行析构函数的特性)
  • 10分钟不使用之后,当然这一机制也可以通过配置来关闭
Buy Me A Coffee / 捐一杯咖啡的钱
分享这篇文章~
0%
//