查询
1. MongoDB使用find来进行查询。find的第一个参数决定了要返回哪些文档,这个参数是一个文档,用于指定查询条件。空的查询会匹配集合的全部内容。要是不指定查询,默认是{}。
2. 可以通过find的第二个参数来指定想要的键。这样即会节省传输的数量,又能节省客户端解码文档的时间和内存消耗。举例:db.users.find({},{"username":1,"email":1})
3. 默认情况下"_id"这个键总是被返回,即便是没有指定想要返回这个键。可以通过第二个参数剔除。
4. 可以通过第二个参数来剔除查询结构中的某个键值对。举例:db.users.find({},{"_id":0}),这样"_id"就不会被返回。
5. 查询条件
1). 查询条件:"$lt"、"$lte"、"$gt"和"$gte"就是全部的比较操作符,分别对应<、<=、> 和>=。举例:db.users.find({"age":{"$gte":18,"$lte":30}})。"$ne" 表示不等于。
2). OR查询:
a. "$in"可以用来查询一个键的多个值: db.raffle.find({"ticket_no":{"$in":[725,542,390]}})
b. "$nin"将返回与数组中所有条件不匹配的内容: db.raffle.find({"ticket_no":{"$nin":[725,542,390]}})
c. "$or"可以在多个键中查找任意给定的值: db.raffle.find({"$or":[{"ticket_no":725},{"wenner":true}]})
3). $not"
a. "$mod"取模运算符,"$mod"将会查询的值除以第一个给定值,若余数等于第二个给定的值则匹配成功:db.users.find({"id_num":{"$mod":[5,1]}})
b. 如果查询上一条相反的结果:db.users.find({"id_num":{"#not":{"$mod":[5,1]}}})。
c. "$not"与正则表达式联合使用时,极为有用,用来查找那些特定模式不匹配的文档。
4). 条件语义: 条件语句是内层文档的键,而修改器则是外层文档的键。
6. 使用普通的AND型查询时,总是希望尽可能用最少的条件来限定结果的范围。OR型查询正相反:第一个查询条件应该尽可能匹配更多的文档,这样才是最为高效的。"$or"在任何情况下都会
正常工作。如果查询优化器可以更高效地处理"$in",那就选择使用它。
7. 特定查询条件:
1). null : db.c.find({"y":null}) 。null 不仅会匹配某个键的值为null的文档,而且还会匹配不包含这个键的文档。
如果仅想匹配键值值为null的文档,既要检查该键的值是否为null,还要通过"$exists"条件判定键值已存在。db.c.find({"z":{"$in":[null],"$exists":true}})
2). 正则表达式:
a. 忽略大小写:查找所有名为Joe或者joe的用户:db.users.find({"name":/joe/i}) 。i 为 ignore ,
b. 匹配任意字符串:db.users.find({"name":/joey?/i})
c. MongoDB可以为前缀型正则表达式(比如/^joey/)查询创建索引,所有这种类型的查询会非常高效。
3). 查询数组
a. db.food.find({"fruit":"banana"}) 匹配包含banana的元素
b. $all 配置多个元素:db.foo.find({fruit:{$all:["apple","banana"]}}) 这里顺序无关紧要。
精确匹配:db.food.find({"fruit":["apple","banana","peach"]})
查询数组特定位置元素,使用key.index语法指定下表:db.food.find({"fruit.2":"peach"})
c. $size 查询特定长度的数组。db.food.find({"fruit":{"$size":3}})。$size并不可能与其他查询条件组合使用,但是这种查询可以通过在文档中添加一个size键的方式来实现。
d. $slice操作符:可以返回某个键匹配的元素的一个子集。db.blog.posts.findOne(criteria,{"comments":{"$slice":10}}) 返回前10条评论,如果是负数,返回后后十条元素。
db.blog.posts.findOne(criteria,{"comments":{"$slice":[23,10]}})。这个操作会跳过23个元素,返回第24~33个元素
4). 返回一个匹配的数组元:db.blogs.posts.find({"comments.name":"bob",{"comments.$":1}}) 返回第一个匹配的文档
5). 数组和范围查询的相互作用:
1). 可以使用$elemMatch要求MongoDB同时使用查询条件中的两个语句与一个数组元素进行比较,"$elemMatch"不会匹配非数组元素:db.test.find({"x":{"$elemMatch":{"$gt":10,"$lt":20}}}) , 只匹配数组
2). 如果创建过索引,可以使用min()和max()将查询条件遍历的索引范围限制为"$gt"和"$lt"的值:db.test.find({"x":{"$gt":10,"$lt":20}}).min("x":10}).max({"x":20})
6). 查询内嵌文档:
加入有文档:{"name":{"firts":"joe","last":"Schmoe"},"age":45}
a. 查询整个文档:db.people.find({"name":{"first":"Joe","last":"Schmoe"}}) 如果上面的文档name中包含其他键,则查询条件不满足
b. 只针对键值对查询:db.people.find({"name.first":"Joe","name.last":""})
c. "$element" 这种模糊的命名条件语句能用来在查询条件中部分指定匹配数组中的单个内嵌文档:
db.blog.find({"comments":{"$elemMatch":{"author":"joe","score":{"#gte":5}}}}) 满足里面的所有条件。(a 中是否满足任意条件都会被返回???)
7). $where : 功能最强大的查询语句,它可以在查询中执行任意的JavaScript。不是非常必要时,一定要避免使用$where查询,因为他们在速度上要比常规查询慢很多。
8. 游标:游标(cursor)是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果。
1). limit, skip ,sort
a. 使用limit函数限制结果数量:db.c.find().limit(3)
b. 使用skip跳过部分数据:db.c.find().skip(3)
c. 使用sort进行排序,1 升序,-1 降序:db.c.find().sort({username:1,age:-1})
使用上面三种组合可以实现分页:db.stock.find({"desc":"mp3"}).limit(50).skip(50).sort({"price":-1})。然而略过过多的结果会导致性能问题。
d. 不同类型也可以进行排序,具体排序规则参见具体文档。
2). 避免使用skip略过大量结果
a. skip暂时无法使用索引
b. 不适用skip对结果分页:可以考虑使用上一次查询的结果,作为下一次查询的条件来实现分页。大于等于上一条记录的最大值。
3). 高级查询
a. $maxscan:integer 指定本次查询中扫描文档数量的上线。
b. $min : document 强制指定一次索引扫描的下边界
c. $max : document 强制指定一次索引扫描的下上边界。
d. $showDisLoc:true 显示该条在磁盘上的位置。
4). 获取一致结果:使用快照保证每个文档只被返回一次。db.foo.find().snapshot() 快照会使查询变慢,所以只在必要的时候使用快照。
9. 看待游标有两种角度:客户端的游标以及客户端游标表示的数据库游标。在服务器端,游标消耗的内存和其他资源。游标遍历尽了结果以后,或者客户端发来消息要求终止,数据库将会释放这些资源。
还有一些其他情况会导致游标终止,随后被清理。如果想避免超时销毁,可以通过immortal的函数或者类似机制,来告知数据库不要让游标超时。
10. 数据库总会返回一个包含"ok"键的文档。如果"ok"的值是1,说明命令执行成功了;如果只返回0,说明一些原因,命令执行失败了。这时会有一个额外的键"errmsg"返回,里面描述失败的原因。
索引
1. 不使用索引的查询称为全表扫描
2. 在username字段上创建索引:db.users.ensureIndex({"username":1})
3. 使用了索引的查询几乎可以瞬间完成,但使用索引是有代价的:对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据变动时,MongoDB不仅要更新文档,
还要更新集合上的所有索引。
4. 为了选择合适的键来建立索引,可以查看常用的查询,以及那些需要被优化的查询,从中找出一组常用的键。
5. 复合索引:db.users.ensureIndex({"age":1,"username":1})
6. 使用索引键对文档进行排序非常快。然后,只有在首先使用索引键进行排序时,索引才有用。
7. MongoDB对复合索引的使用方式:
1). 点查询:db.users.find({"age":21}).sort({"username":-1})
2). 多值查询:db.users.find({"age":{"$gte":21,"$"}})
3). db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1}) 这个索引得到的结果集中"username"是无序的,所以MongoDB需要先在内存中对结果进行排序,然后才能返回。
所以效率不如上一个。
4). 如果结果集的大小超过32M MongoDB就会出错。
8. 可以通过explain()来查看MongoDB对查询的默认行为。
9. 可以通过使用hint()来强制MongoDB使用某个特定的索引。
10. 通常来说,如果MongoDB使用索引进行查询,那么查询结果文档通常是按着索引顺序排列的。
如果对查询的结果范围作了限制,那么MongoDB在几次匹配之后就可以不再扫描索引,在这种情况下,将排序键放在第一位是一个非常好的策略。
11. db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1}) mongoDB需要先在内存中对结果进行排序,然后才能返回。
12. 在实际应用程序中,{"sortKey":1,"queryCriteria":1} 索引通常是很有用的(可以减少内存排序,但需要扫描全表,所以一般在有数量限制的时候使用),因为大多数应用程序在一次查询中只需要得到查询结果最前面的少数结果。
13. 索引的本质是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上。
14. 应该尽可能让索引是右平衡,即让新的、使用最多的数据,在索引遍历开始的一个方向。"_id"索引就是一个典型的右平衡索引。
15. 使用复合索引
1). 选择键的方向:到目前为止,我们的所有索引都是升序的(或者从小到大的)。 为了在不同方向上优化这个复合查询,需要使用与方向相匹配的索引。例如:{"age":1,"username":-1}
只有基于多个查询条件进行查询时,索引的方向才比较重要。如果只是基于单一键排序,MongoDB会在使用索引时进行优化,MongoDB可以简单地从相反方向读取索引。所以不要创建两个相反方向的索引。
2). 使用覆盖索引:当一个索引包含用户请求的所有字段,可以认为这个索引覆盖了本次查询。在实际中,应该优先使用覆盖索引,而不是去获取实际的文档。为了确保查询只使用索引就可以完成,应该
使用投射来指定不要返回"_id"字段(除非它是索引的一部分)。可能还需要对不需要查询的字段做索引,因此需要在编写时就在所需的查询速度和这种方式带来的开销之间做好权衡。
3). 隐式索引:复合索引居右双重功能,而且对不同的查询可以表现为不同的索引。如果有一个{"age":1,"username":1}索引,"age"字段会被自动排序,就好像有一个{"age":1}索引一样。
因此这个复合索引可以当作{"age":1}索引一样使用。
这个可以根据需要推广到尽可能多键:如果有一个拥有N个键的索引,那么你同时"免费"得到了所有这N个键的前缀组成的索引。注意:只有能够使用索引前缀的查询才能从中受益。
16. $操作符如何使用索引
1). 低效率的操作符:
a. 有一些查询完全无法使用索引:$where , $exists
b. 通常来说取反的效率比较低。"$ne"查询可以使用索引,但并不是很有效。$not和$nin一般情况下都会进行全表扫描。如果需要快速执行一个这类型的查询,可以试着找到另一个能够使用索引
的语句,将其添加到查询中,这样就可以在MongoDB进行无索引匹配时,先将结果集的文档数量减到一个比较小的水平。例如:
db.users.find({"birthday":{"$exists":false},"_id":{"$lt":march20Id}}})
这个查询中的字段顺序无关紧要,MongoDB会自动找出可以使用索引的字段,而无视查询中的字段顺序。
c. 根据范围决定索引顺序:复合索引使MongoDB能够高效地执行拥有多个语句的查询。设计基于多个字段的索引时,应该将会用于精确匹配的字段(比如"x":"foo")放在索引的前面,将用于索引
匹配的字段(比如"y":{"$gt":3,"$lt":5})放在最后。这样查询就可以先使用第一个索引键进行精确匹配,然后再使用第二个索引范围在这个结果集内进行搜索。
d. OR查询:MongoDB在一次查询中只能使用一个索引。"$or"是个例外,"$or"可以对每个子句都是用索引,因为"$or"实际上是执行两次查询然后将结果合并,去除重复文档。
17. 索引对象和数组
1). 索引嵌套文档:db.users.ensureIndex({"loc.city":1}) 。可以对这种方式对任意深层次的字段建立索引。注意,对嵌套文档本身("loc")建立索引,与对嵌套文档的某个字段("loc.city")
建立索引是不同的。对整个子文档建立索引,只会提高整个子文档的查询速度。
2). 索引数组:对数组建立索引,这样就可以高效地搜索数组中的特定元素。对数组建立索引,实际上是对数组的每一个元素建立一个索引条目,所以如果一个篇文章有20条评论,那么它就拥有20
个索引条目。因此数组索引的代价比单值索引高。db.blog.ensureIndex("comments.date":1)
3). 多键索引:对于某个索引的键,如果这个键在某个文档中是一个数组,那么这个索引就会被标记为多键索引。多键索引可能会比非多键索引慢一些。可能会有多个索引条目指向同一个文档,因此
MongoDB在返回结果集时必须要先去除重复内容。(好麻烦的样子!)
18. 索引基数:基数就是集合中某个字段拥有不同值得数量。通常一个字段的基数越高,这个键上的索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的集合。
19. 使用explain() 和 hint()
1). explain() 能够提供大量与查询相关的信息。对于速度比较慢的查询来说,这是最重要的诊断工具之一。通过查看一个索引的explaint()输出信息,可以知道查询使用了哪个索引,以及是如何使用
的。对于任意查询,都可以在最后添加一个explain()调用(与调用sort()或者limt()一样,不过explain()必须放在后面).
2). 如果发现MongoDB使用的索引与自己希望它使用的索引不一致,就可以使用hint()强制MongoDB使用特定的索引。
20. 查询优化器:一个索引能够精确匹配一个查询,那么查询优化器就会使用这个索引。不然的话,可能会有几个索引都适合你的查询。MongoDB会从这些可能的索引子集中为每次查询计划选择一个,
这些查询计划是并行执行的。最终返回100个结果的就是胜利者,其他的查询计划就会被终止。这个查询计划就会被缓存,这个查询接下来都会使用它,直到集合数据发生了比较大的变动。
21. 何时不应该使用索引:结果集在元集合中所占的比例越大,索引的速度就越慢,因为索引需要进行两次查找:一次是查找索引条目,一次是根据索引指针去查找响应的文档。而全表扫描只需要进行一次
查找:查找文档。一般情况下,如果查询需要返回集合内30%的文档(或者更多),那就应该对索引和全表扫描的速度进行比较。然而这个数字可能会在2%~60%主键变动。
影响索引效率的属性:
索引通常适用的范围:集合较大,文档较大,选择性查询
全表扫描通常适用的情况:集合较小,文档较小,非选择性查询
22. 索引类型:
唯一索引:唯一索引可以确保集合的每一个文档的指定键都有唯一值。如果一个文档没有对应的键,索引会将其作为null存储。所以,如果对某个键建立了唯一索引,但插入了多个缺少该索引键的文档,
由于集合已经存在一个该索引键的值为null的文档而导致插入失败。db.users.ensureIndex({"username":1},{"unique":true})
索引储桶的大小是有限制的,如果某个索引条目超出了它的限制,那么这个索引条目就不会包含在索引里。这样就会造成一些困惑,因为使用这个索引进行查询时会有一个文档凭空消失不见了。
所有的字段都必须小于1024字节,才能包含到索引里。如果一个文档的字段太大不能包含在索引里,MongoDB不会返回任何错误信息。也就是说,超出8KB大小的键不会受到唯一索引的约束:
可以插入多个同样的8KB长的字符串。
1). 复合唯一索引:单个键的值可以相同,但所有键的组合值必须是唯一的。
2). 去除重复:db.people.ensureIndex({"username":1},{"unique":true,"dropDups":true}) 强制建立索引,第一个相同值被保留,其余值删除。此方法过于粗暴!
稀疏索引:MongoDB的稀疏索引与关系型数据库中的稀疏索引是完全不同的概念。如果有一个可能存在可能不存在的字段,但是当它存在时,他必须是唯一的,这时就可以使用unique和sparse选项组合
在一起使用。 db.ensureIndex({"email":1},{"unique":true,"space":true})
稀疏索引不必是唯一的。主要去掉unique选项,就可以创建一个非唯一的稀疏索引。稀疏索引查询时,会忽略那些不包含指定索引的数据。
23. 索引管理:可以使用ensureInde函数创建一个新的索引。对于一个集合,每个索引只能创建一次。如果重复创建相同的索引,是没有任何作用的。
所有的数据库索引信息都存储在system.indexes集合中。这是一个保留集合,不能在其中插入或者删除文档。只能通过ensureIndex或者dropIndexes对其进行作用。
24. 标识索引:集合中的每一个索引都有一个名称,用于唯一标识这个索引,也可以用于服务器端来删除或者操作索引。索引的名称默认为:keyname1_dir1_keyname2_dir2……其中keynameX是索引的键,
dirX是索引的方向(1或者-1)。可以使用ensureIndex指定索引的名称,索引名称长度有限制,所以新建索引时可能需要自定义索引名称。调用getLastError就可以知道索引是否创建成功。
25. 修改索引:可以通过使用dropIndex命令删除不需要的索引。
新建索引是一件既浪费时间又浪费资源的事。默认情况下,MongoDB会尽快地创建索引,阻塞所有数据库的请求操作,一直到所以创建完成。可以在创建索引时指定backgroud选项后台创建索引,
后台创建索引可以继续处理请求,但创建索引效率更低 。
26. 通常来说,执行两次查询再讲结果合并的效率不如单次查询高,因此,应该尽可能使用"$in"而不是"$or"。