mongodb&&mongoose[进行中]

最近做的几个项目都用到了mongodbmongoose,以前整理过一份常用api,重温一次再做补充。

shell

  • show dbs,show databases
  • show collections,show tables
  • show users
  • show profile
  • use \
  • db.help()
  • db.auth
  • db.addUser

数据类型

  • null
  • boolean
  • 64位浮点数(js),32位整数,64位整数,64位浮点数
  • string
  • date
  • ObjectId
  • regexp
  • code javascript代码
  • buffer
  • undefined
  • array
  • document
  • max/min

ObjectId() 12字节存储,每个字节两位16进制数字。
0-3字节位时间戳,4-6字节主机唯一标示,7-8位pid标识不同进程,9-11位计数器。
所以同一秒同一进程最多可产生256^3(16777216)个不同的ObjectId。

mongodb中不存储时区,从标准纪元开始的ms数。shell显示时会读取本地的时区设置做对应转换。

基本操作接口

curd

create

post = {}
db.blog.insert(post);

update

post = {content:’test’}
db.blog.update({title:”post”},post);

find

db.blog.find({title:”post”})

remove

db.blog.remove({title:”post”});

插入

db.foo.insert({“bar”:”baz”});

对同一集合插入时,尽量使用批量插入,但是批量插入消息长度最大为16MB。
单个文档(转换为BSON)不能大于4MB。shell中使用 Object.bsonsize(doc)查看大小。

删除文档

db.users.remove([optional doc]) 删除users集合中所有文档,但不会删除集合本身,原有的索引也会保留。

remove函数接受一个查询文档作为可选参数。删除数据时永久性的,不能撤销也不能恢复。
db.users.list.remove({“opt-out”:true})

更新文档

更新操作是原子的。如果两个更新同时发生,先到达服务器的先执行,最后到达服务器的最后执行。

两个参数,一个是查询文档,一个时修改器(modifier)文档
db.users.update(finddoc,modifier)

修改器

$inc

增加,键不存在时自动创建。只能用于数字类型的键
增加 db.users.update(_id,{“$inc”:{score:50}}) 增加50分

$set

指定一个键的值,如果这个键不存在则创建它
可以修改数据类型,也可以修改内嵌文档

1
2
db.users.update({_id:ObjectId('asdasdasd')},{"$set":{"address":"test"}})
db.users.update(_id,{"$set":{"author.name":"joe"}})

$unsert

删除键

1
db.users.update(_id,{"$unsert":{"address":1}})

$push

数组修改器,向数组末尾加入一个元素,如果键不存在则创建一个数组

1
db.users.update(_id,{"$push":{"address":{"city":"taiyuan","street":"as"}}})

$ne 使用在查询文档

如果一个值不在数组中就把它加进去

1
db.papers.update({"autjprs cited":{"$ne":"Richie"}},{"$push":{"authors cited":"Richie"}})

$addToSet

1
db.users.update(_id,{"$addToSet":{"emails":"test@t.com"}})

$each

一次添加多个邮件地址

1
db.users.update(_id,{"$addToSet":{"emails":{"$each":["a@a.com",'a@b.com','a@c.com']}}})

$pop

移除一个元素

1
2
db.lists.update(_id,{"pop":{"emails":1})  //从末尾移除
db.lists.update(_id,{"pop":{"emails":-1}) //从头部移除

$pull

移除匹配条件的数组元素

1
db.lists.update(_id,{"$pull":{"email":"a@a.com"}})

数组定位操作符$

通过位置活着定位操作符$

1
2
{"$inc":{"comments.0.votes":1}} //第一条评论的投票数+1
db.blog.update({"comments.author":"john",{"$set":{"comments.$.author":"jim"}}})

定位符只更新第一个匹配的元素。

upsert

如果没有找到符合条件的文档,则insert一个新的文档。如果找到,则更新该文档。

1
db.analytics.update({"url":"/blog"},{"$inc":{"visits":1}},true)

第三个参数true表示该次update为upsert

save

文档存在时插入,存在时更新。
如果文档包括”_id”键,则会调用upsert,否则调用插入

更新多个文档,使用update的第四个参数

第4个参数true则更新所有文档,为false则只更新第一个符合条件的文档。默认为false

1
db.users.update({birthday:"2014-01-01",{$set:{gift:"Happy birthday!"}}},false,true)

db.runCommand({getLastError:1})

1
2
3
4
5
6
{
"err":null,
"updatedExisting":true,//表明对已有的文档进行更新
"n":5,//说明有5个文档被更新了
"ok":true
}

findAndModify 命令

每个键对应如下:

  • findAndModify 字符串,集合名
  • query 查询文档,用来检索文档的条件
  • sort 排序结果的条件
  • update 修改器文档
  • remove boolean,表示是否删除文档
  • new 表示返回的事更新前的文档还是更新后的。默认是更新前的文档
  • update和remove只能有一个,必须有一个
    只能处理一个文档,不能直行upsert操作,只能更新已有文档。

插入、删除和更新都是瞬间完成的

查询

find(条件文档,指定返回的键)

findOne的参数和find一致

指定返回的键

{“username”:1,”email”:1,”address”:0} 表示返回username,email,_id,不返回address

条件文档

  • $lt <
  • $lte <=
  • $gt >
  • $gte >=
  • $ne <>
  • $or or
  • $in in
  • $nin not in
  • $mod [a,b] 将查询的值除a,若余数等于b则返回该结果
  • $not 元条件句,可用于任何其他条件之上。
  • $exists 键是否存在 db.users.find({“address”:{“$exists”:true}})
  • 正则表达式 db.users.find({“name”:/joe/i})

mongodb使用perl兼容的正则表达式(PCRE)库来匹配正则表达式
mongodb可以为前缀型正则表达式(比如/^joe/)查询创建索引,所以这类型的查询会非常高效。

1
2
3
4
5
6
7
8
9
10
11
12
start = new Date("2014-01-01")
db.users.find({"registered":{"$lt":start}})
db.users.find({"user_id":{"$in":['1234','joe']}})
//查找user_id为joe或年龄为20
db.users.find({"$or":[{"user_id":"joe"},{"age":20}])
//返回id_num为1,6,11,16等的用户
db.users.find({"id_num":{"$mod":[5,1]}})
//返回id_num不为1,6,11,16等的用户
db.users.find({"id_num":{"not":{"$mod":[5,1]}}})

//条件句是内层文档的键,修改器是外层文档的键,可对一个键应用多个条件
db.users.find({"age":{"$lt":30,"$gt":20}}) 查找年龄小于30大于20
查询数组
1
2
db.food.insert({"fruit":["a","b","c"]})
db.food.find({"fruit":"a"})

可以将数组类型的键理解为:数组的每个元素都是整个键的值

$all 多个元素来匹配数组,查找包含$all参数的数组

也可以直接使用数组来精确匹配,精确匹配时会检查数组顺序

使用key.index语法指定下标来查询指定位置的元素

$size 查询长度,$size不能和其他查询子句组合

$slice 指定要返回的长度

1
2
3
4
db.blog.posts.find({},{"comments":{"$slice":10}}) //返回前10
db.blog.posts.find({},{"comments":{"$slice":-10}}) //返回后10
//返回后第24-33个元素,如果不够10个则返回23个元素后面的所有元素
db.blog.posts.find({},{"comments":{"$slice":[23,10]}})

第二个参数用了$slice后,将返回文档所有的键,除非特别声明不返回

查询内嵌文档
1
db.people.find({"name.first":"joe","name.last":"Schmoe"})

$elemMatch 将限定条件进行分组,仅当需要对一个内嵌文档的多个键操作时才会用到

1
db.blog.find({"comments":{"$elemMatch":{"author":"joe","score":{"$gte":5}}})

在博客中查找作者为joe并且评分大于5的评论

下面一个查询时错误的:

1
db.blog.find({"comments.author":"joe","comments.score":{"$gte":5}})

这会直接返回这条blog

$where

1
2
3
4
5
6
7
8
9
10
db.foo.find({"$where":function(){
for(var current in this){
for(var other in this){
if (current != other && this[current]== this[other]){
return true;
}
}
}
return false;
}})

如果函数返回true,则返回该文档。

1
2
db.foo.find({"$where":"this.x + this.y==10"})
db.foo.find({"$where":"function(){return this.x+this.y==10;}"})

尽可能用索引作为前置过滤,然后使用$where对结果进行调优

游标

1
2
3
4
var cursor = db.collection.find()
while(cursor.hasNext()){
obj = cursor.next()
}

hasNext() 检测有没有其他结果

next() 返回一条结果

forEach() 迭代游标中的结果

find方法总是会返回游标本身,只有要求获得结果时才发送查询,所以可以给游标附加额外的选项

1
db.foo.find().sort({"x":1}).limit(1).skip(10)

limit 指定返回结果的上限
skip 指定忽略结果的数量的上限
sort 排序,键值对,键对应文档的键名,值代表排序的方向,1为升序,-1为降序,可指定多个键值对。

1
db.c.find().sort({username:1,age:-1})

对混合类型的键排序

如果一个键例如user_id为混合类型的,包括数字、字符串等,则按照以下顺序排序从小到大:
1.最小值
2.null
3.数字
4.字符串
5.对象/文档
6.数组
7.二进制数据
8.对象ID
9.布尔
10.日期
11.时间戳
12.正则表达式
13.最大值

分页

避免使用skip略过大量结果
可使用上一次查询的结果作为查询条件来获取下一页

随机选取文档

可以给文档增加一个额外的随机键”random”:Math.random()
然后根据random来查找

1
2
3
4
5
random = Math.random()
result = db.foo.findOne({"random":{"$gt":random}})
if(result == null){
result = db.foo.findOne({"random":{"$lt":random}})
}

索引

1
db.people.ensureIndex({"username":1})