查询
find
使用find进行查询,返回一个集合中文档的子集,子集合的范围从0个文档到整个集合。 find ( query , fields , limit , skip, batchSize, options ) * query:查询器文档,决定了要返回哪些文档,这个参数说明了要执行的查询细节 1. 默认为{},表示匹配集合的全部内容。例如:> db.c.find() 2. 支持多个键/值对的方式将多个查询条件组合在一起。例如:> db.users.find({"username":"joe","age":27}) * fields:指定返回的键 1. 使用此参数的好处:节省传输的数据量,节省客户端解码文档的时间和内存消耗 2. 使用举例:> db.users.find({},{"username":1,"emails":1}) 3. 注意,"_id"这个键总是被返回,即便是没有指定也一样 4. 也可以用来剔除查询结果中的某个键/值对。例如:> db.users.find({},{"emails":0}) * limit * skip * batchSize * options
查询条件
比较查询
四种比较操作符:"\(lt"(<), "\)lte"(<=), "\(gt"(>), "\)gte"(>=) 1
2# 查询在18~30岁(含)的用户
> db.users.find({"age":{"$gte":18,"$lte":30}})1
2# 查询所有名字不为joe的用户
> db.users.find({"username":{"$ne":"joe"}})1
2
3
4
5
6
7
8 在raffle中查找ticket_no在[725,542,390]中的文档
"$in"还可以指定不同类型的条件和值
db.raffle.find({"ticket_no":{"$in":[725,542,390]}})
"$nin"将返回与数组中所有条件都不匹配的文档
下面这个语句就是查找ticket_no不在[725,542,390]中的文档
db.raffle.find({"ticket_no":{"$nin":[725,542,390]}})
"$or"接受一个包含所有可能条件的数组作为参数
db.raffle.find({"$or":[{"ticket_no":725},{"winner":true}]})
\(not "\)not":元条件句,可以用在任何其他条件之上。与正则表达式联合使用,可以用来查找那些与特定模式不符的文档。
条件句 V.S 修改器
- 条件句是内层文档的键,修改器则是外层文档的键
- 可以对一个键应用多个条件,但是不能对一个键应用多个更新修改器
特定于类型的查询
null
- null可以匹配自身
- 还可以匹配不存在。也就是说,这种匹配会返回缺少这个键的所有文档
1
2
3
> db.users.find({"name":null}) - 若只想匹配键值为null的文档,既要检查该键的值是否为null,还要通过"$exists"条件判定键值已存在 ### 正则表达式 MongoDB使用Perl兼容的正则表达式(PCRE)库来匹配正则表达式。
1
> db.c.find({"z":{"$in":[null],"$exists":true}})
### 查询数组1
2
3
4
5
6
7# 忽略大小写:查找所有名为Joe或者joe的用户
> db.users.find({"username":/joe/i})
# 匹配各种大小写组合形式的joe
> db.users.find({"username":/joe?/i})
# 还可以匹配自身
> db.foo.insert({"bar":/baz/})
> db.foo.find({"bar":/baz/})
- $all:匹配一组元素,元素的顺序无关紧要
1
2
3
4
5
6
7> db.food.insert({"_id":1,"fruit":["apple","kumquat","orange"]})
> db.food.insert({"_id":2,"fruit":["apple","kumquat","orange"]})
> db.food.insert({"_id":3,"fruit":["cherry","banana","apple"]})
# 查找既有"apple"又有"banana"的文档
> db.food.find({"fruit":{$all:["apple","banana"]}})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
{ "_id" : 3, "fruit" : [ "cherry", "banana", "apple" ] } - \(size:查询指定长度的数组 注意:"\)size"不能与其他查询子句组合,但是这种查询可以通过在文档中添加一个"size"键的方式来实现。
1
> db.food.find({"fruit":{"$size":3}})
- \(slice:返回数组的一个子集合 注意:如无特别声明,使用\)slice会返回文档中所有键。
1
2
3
4
5返回前10条评论。如果要返回后10条,使用-10即可
db.blog.posts.findOne({},{"comments":{"$slice":10}})
$slice接受偏移值和要返回的元素数量
下面这个语句返回第24~33个元素
db.blog.posts.findOne({},{"comments":{"$slice":[23,10]}})
查询内嵌文档
两种方式: * 查询整个文档:与普通查询完全相同。要注意,这种查询与顺序是相关的。 * 只针对其键/值对进行查询 1
> db.people.find({"name.first":"joe","name.last":"Schemoe"})
\(where查询 使用"\)where"子句,可以执行任意JavaScript作为查询的一部分
1 | > db.foo.insert({"apple":1,"banana":6,"peach":3}) |
注意:如非必要,一定要避免使用"\(where"查询。因为它们在速度上比常规查询慢得多(每个文档都要从BSON转换成JavaScript对象,然后通过\)where表达式运行。而且还不能用索引)
游标
数据库使用游标返回find的执行结果 1
2
3
4
5
6
7
8
9
10
11# 使用cursor变量保存结果
> var cursor = db.c.find()
# 使用游标的next方法来迭代结果,hasNext方法检查是否有后续结果存在
> while (cursor.hasNext()) {
... obj = cursor.next()
... // do stuff
... }
# 游标类实现了迭代器接口,可以在foreach循环中使用
> cursor.forEach(function(x) {
... print (x);
... });1
2
3
4
5
6
7
8
9
10# 限制结果数量,limit指定上限
> db.c.find().limit(3)
# 略过前n个匹配的文档,返回如下的文档。若匹配的文档少于n个,则不会返回任何文档
> db.c.find().skip(3)
# sort用一个对象作为参数:一组键/值对。
# 键对应文档的键名,值代表排序的方向(1:升序;-1:降序)。
# 若指定了多个键,则按照多个键的顺序逐个排序
> db.c.find().sort({x:1,age:-1})
# 可以组合来用。例如:分页
> db.stock.find({"desc":"mp3"}).limit(50).skip(50).sort({"price":-1})
避免使用skip略过大量结果
当skip的数量非常多的时候,会变得很慢,所以要尽量避免 * 不用skip对结果分页:一般来讲,可以找到一种方法实现不用skip的分页。 * 随机选取文档:可以在插入文档时给每个文档都添加一个额外的随机键以避免skip
高级查询选项
两种查询: 1. 普通查询:> var cursor = db.foo.find({"foo":"bar"}) 2. 包装查询:> var cursor = db.foo.find({"foo":"bar"}).sort({"x":1})。这种查询实际上会被转化为db.foo.find({"$query":{"foo":"bar"}, "$orderby": {"x":1}}) 下面是一些有用选项: * $maxscan:integer 指定查询最多扫描的文档数量 * $min:document 查询的开始条件 * $max:document 查询的结束条件 * $hint:document 指定服务器使用哪个索引进行查询 * $explain:boolean 获取查询执行的细节(用到的索引、结果数量、耗时等),而并非真正执行查询 * $snapshot:boolean 确保查询的结果是在查询执行的一致快照
获取一致结果
当查找修改后存回去时,可能会出现由于文档体积增加而预留空间不足造成的移动。通常会将其挪至集合的末尾处。这样的情况下,游标有可能会返回那些已经被挪动的文档。 解决方法是对查询进行快照,也就是使用"$snapshot"选项。这个时候,查询是针对不变的集合视图运行的。
在服务端的游标
游标在服务端会消耗内存和其他资源:当游标遍历尽了结果以后或者客户端发来消息要求终止,数据库才会释放这些资源 导致游标终止(随后被清理)的一些情况: 1. 游标完成匹配结果的迭代时会清理自身; 2. 当游标在客户端已经不在作用域内了,驱动会向服务器发送专门的消息让其销毁游标; 3. 只要10分钟不使用游标,数据库游标就会自动销毁。
索引
索引是用来加速查询的。 当查询中仅使用一个键时,可以对该键建立索引,以提高查询速度。 1
2
3
4
5
> db.users.ensureIndex({"username":1})
> db.posts.ensureIndex({"date":1, "username":-1})
- 使用索引前三问
- 会做什么样的查询?其中哪些键需要索引?
- 每个键的索引方向是怎样的?
- 如何应对扩展?有没有种不同键的排列可以使常用数据更多地保留在内存中?
- 为内嵌文档的键建立索引和为普通的键创建索引没有什么区别。而对内嵌文档的键索引与普通键索引并无差异,二者可以联合组成复合索引。
- 由于做无索引排序时有内存限制的,因此,可以按照排序来索引以便让MongoDB按照顺序提取数据。
- 索引名字
- 每个索引都有一个字符串类型的名字,来唯一标识索引
- 默认情况下,索引名类似
keyname1_dir1_keyname2_dir2_..._keynameN_dirN,其中keynameX代表索引的键,dirX代表索引的方向(-1/1) - 可以自定义索引名字,例如:
> db.foo.ensureIndex({"a":1,"b":-1}, {"name":"alphabet"})
唯一索引
唯一索引可以确保集合的每一个文档的指定键都有唯一值 1
2 创建唯一索引:{"unique":true}
db.users.ensureIndex({"username":1},{"unique":true})dropDups选项保留发现的第一个文档,删除接下来的有重复值的文档。例如:> db.users.ensureIndex({"username":1},{"unique":true, "dropDups:true"}) * 创建复合唯一索引时,单个键的值可以相同,只要所有键的值组合起来不同即可。
explain和hint
- explain:获得查询方面的信息。
1
2
3# explain会返回一个文档
# 文档中包含了查询使用的索引情况(若有),耗时及扫描文档数的统计信息。
> db.users.find({"age":18}).sort({"username":1}).explain() - hint:当发现MongoDB用了非预期的索引,可以用hint强制使用某个索引 ## 索引管理 索引的元信息存储在每个数据库的
1
> db.c.find({"age":14, "username":/.*/}).hint({"username":1,"age":1})
system.indexes集合中。这个是个保留集合,不能对其插入或删除文档。操作只能通过ensureIndex或者dropIndexes进行。system.namespaces集合含有索引的名字。里面包含了两种信息,集合本身和对应集合包含的索引。 注意:集合名和索引名加起来不能超过127字节。为已有文档创建索引比先创建索引再插入所有文档要快一点。1
2# 使用background选项让建立索引操作在后台完成,同时不阻塞正常请求
> db.users.ensureIndex({"username":1},{"background":true})## 地理空间索引 地理空间索引:MongoDB为坐标平面查询提供的专门的索引1
2
3
4
5
6
> db.runCommand({"dropIndexes":"foo","index":"alphabet"})
> db.runCommand({"dropIndexes":"users","index":"*"})# 聚合 ## count:返回集合中文档的数量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17参数"2d":创建地理空间索引
"gps"键的值必须是某种形式的一对值:
一个包含两个元素的数组或是包含两个键的内嵌文档
db.map.ensureIndex({"gps":"2d"})
默认情况下,地理空间索引假设值的范围是-180~180
可以通过ensureIndex选项来指定最大最小值
db.star.trek.ensureIndex({"light-years":"2d"},{"min":-1000,"max":1000})
查询方法一:使用find命令+"$near"
按照离点(40,-73)由近到远的方式将map集合的所有文档都返回
db.map.find({"gps":{"$near":[40,-73]}})
查询方法二:使用geoNear命令
geoNear还会返回每个文档到查询点的距离。
db.runCommand({geoNear:"map",near:[40,-73]})
创建复合地理空间索引
db.map.ensureIndex({"location":"2d","desc"1})
查找
db.map.find({"location":{"$near":[-70,30]},"desc":"coffeeshop"})## distinct:找出给定键的所有不同的值1
2
3
4
5
> db.foo.count()
> db.foo.count({"x":1})## group 过程:选定分组所依据的键 -> 将集合依据选定键值的不同分成若干组 —> 聚合每一组内的文档 -> 产生一个结果文档1
2# 使用distinct时必须指定集合和键
> db.runCommand({"distinct":"users", "key":"age"})## MapReduce MapReduce会拆分问题,再将各个部分发送到不同的机器上,让每台机器都完成一部分。当所有机器都完成的时候,再把结果汇集起来形成最终完整的结果。1
2
3
4
5
6
7
8
9
10
11
12> db.runCommand({"group": {
... "ns":"stocks", # 指定要进行分组的集合
... "key":"day", # 指定文档分组依据的键
... "initial":{"time":0}, # 每一组reduce函数调用的初始时间,会作为初始文档传递给后续过程。每一组的所有成员都会使用这个累加器
... "$reduce":function(doc,prev) { # 每个文档都对应一次这个吊用。系统传递两个参数:当前文档和累加器文档
... if (doc.time > prev.time) {
... prev.price = doc.price;
... prev.time = doc.time;
... }
... }}})
# "finalizer":完成器,用以精简从数据库传到用户的数据
# "$keyf":定义分组函数
MapReduce的步骤: 1. 映射(map):将操作映射到集合中的每个文档。 2. 洗牌(shuffle):按照键分组,并将产生的键值组成列表放到对应的键中 3. 化简(reduce):把列表中的值化简成一个单值。接着进行洗牌,直到每个键的列表只有一个值为止,这个值就是最后结果。
使用MapReduce的代价,就是速度。
1 | > map = function() # 使用emit函数“返回”要处理的值 |
MapReduce命令的其他可选的键: * "finalize":函数 将reduce的结果发送给这个键,这是处理过程的最后一步 * "keeptemp":布尔 连接关闭时临时结果集合是否保存 * "output":字符串 结果集合的名字。设定该项隐含keeptemp:true * "query":文档 会在发往map函数前,先用指定条件过滤文档 * "sort":文档 在发往map前先给文档排序 * "limit":整数 发往map函数的文档数量的上限 * "scope":文档 JavaScript代码中要用到的变量 * "verbose":布尔 是否产生更详尽的服务器日志