一、创建、更新及删除文档

创建

  • 创建一个 db.foo.insert();

  • 批量插入

    • 减少tcp请求
    • 文档发送到数据库会有头部信息,标志是否是插入
    • 不能插入多个文档到多个集合
    • mongodb消息最大为16mb
  • 插入原理

    • 将数据转换成bson
    • 检验是否包含_id键,并且文档不超过4mb
  • 副作用

    • 无效数据
    • 好处:无注入式攻击风险

删除

db.user.remove

  • 删除所有文档
  • 保留集合和原有索引
  • 数据无法恢复
  • 删除整个集合和索引速度会更快,db.drop_collection(‘coolection_name’);

更新文档

两个参数

  • 一个查询文档
  • 一个更新文档

冲突解决

  • 最后的执行胜利

文档替换

1
2
3
4
5
6
db.collection.update{query,document}
joe = db.people.findOne({"name":});
{"_id" : ObjectId("numbers"),"name" : "joe","age" : 20}
joe.age++;
db.people.update({"name" : "joe"}, joe);
duplicate key on update

修改文档

db.blog.update({a:1},{“$inc“:b});

  • $set

    • 设置字段
    • 插入与更新
    • 可以修改字段类型
  • $unset删除字段

    • 不使用$set修改会将文档覆盖
  • $inc

    • {“$inc“:{“score”:50}}
    • 只能用于整数、浮点数、双精度浮点型
    • 不能自动进行类型转换

数组修改器

  • $push向数组末尾添加一个元素
  • 判断值不在数组中,插入到数组
1
db.blog.update({a:{`$ne`:123}},{`$push`:{a:123}});
  • $addToSet
1
2
db.users.update({"_id" : ObjectId("fljkasdfjkjklw234123kjv")},...\n{"$addToSet":{"emails":{"$each":["joe@php.net", "joe@example.com", "joe@python.org"]}}})
使用addtoset和each可以添加多个数据
  • $pop从数组删除一个元素
1
{$pop:{key:1}} //从末端删除
1
{$pop:{key:-1}} //从头部删除
  • $pull根据条件删除元素
1
db.lists.update({}, {"$pull" : {"todo" : "laundry"}});
  • 定位修改器
1
2
db.blog.update({"post": post_id}, {"$inc":{"comments.0.votes":1}}); //0,1,2,3
db.blog.update({"post": post_id}, {"$inc":{"comments.$.votes":1}}); //定位符$

note

  • 需要修改文档大小的操作比较慢(数组大小越大,修改查询速度越慢)
  • mongodb会预留一部分空白来适应文档大小的改变

upsert

  • 文档存在更新,不存在插入
  • db.blog.update({},{},true);
  • 第三个参数代表upsert
  • 原子性

save

  • db.blog.save(document); 文档中有_id则更新,没有则插入

多文档更新

update的第四个参数为是否多文档更新

查看文档更新的数量:db.runCommand({getLastError : 1});

1
2
3
4
5
6
{
"err" : null,
"updatedExisting" : true, // 对已有文档更新
"n" : 5, //更新的数量
"ok" : true,
}

findAndModify

原子操作

  • 例子
1
2
3
4
5
6
7
8
9
10
11
12
13
ps = db.runCommand(
{
"findAndModify" : "processes", //集合名称
"query" : {"status" : "READY"}, //查询文档
"sort" : {"priority" : -1}, //排序结果的条件
"remove" : true, // 是否删除
"update" : {}, //对找到的文档进行修改 update与remove只能存在一个,只能操作一个文档,不能执行upsert
"new" : true, //返回更新后的文档还是更新前的文档
}
).value;
do_something(ps);

速度相当于执行一次更新,一次查找和一次getLastError的时间

瞬间完成

  • 客户端将更新文档的请求发送给服务端,服务端不会返回状态,不管有没有服务器都会成功
  • 默认是不会有返回结果的,但是mongodb提供了安全版本,执行语句结束之后,立即执行getLastError

是否选择安全版本的条件

  • 不考虑安全性的数据
  • 需要安全监测-付费系统、账号系统、电子邮件
  • 零散信息-页面、广告、统计信息
  • 开发过程中使用安全版本可以方便调试

请求与连接

  • 数据库为每个mongodb创建一个队列,存放请求
  • 一个连接请求不一定能查询到另外一个连接请求的插入数据,特别是使用连接池的时候

二、查询

find

1
2
3
4
5
6
db.c.find(); //返回全部文档
db.users.find({"age" : 27}) //返回文档中age为27的文档
db.users.find({"username" : "joe", "age" : 27}); //默认返回全部字段
db.users.find({}, {"username" : 1, "email" : 1}); //返回username和email字段 _id键总是会被返回,无论是否指定
db.users.find({}, {"fatal_weakness" : 0}); //防止返回字段:fatal_weakness
// 查询文档的值必须是常量

查询条件

比较操作符:

  • "$lt" <
  • "$lte" <=
  • "$gt" >
  • "$gte" >=
  • "$ne" !=

$ne能用于所有类型的数据

or查询

  1. $in
    • 兼容不同的类型
  2. $nin
    • 返回与数组中条件都不匹配的文档
  3. $or

    • in只能对单个键查询
    • or可以对多个键进行查询
    • and查询将最严苛的条件放在最前面,or查询相反
  4. $not

    • $not与正则表达式联合使用极为有用
  5. 条件语句的规则

    • 条件句在内层文档 db.users.find({"age":{"$lt":30,"$gt":20}});
    • 修改器在外层文档 {"$inc" : {"age" : 1}}

特定类型的查询

null

  • 匹配自身
  • 匹配不存在的
  • 返回缺少这个键的文档

想要匹配键值为null的文档
db.c.find(
“z” : {“$in“:[null], “$exists”:true}
);

正则表达式

db.users.find({"name" : /joe/i});

索引:
mongodb会为前缀型正则表达式查询创建索引

查询数组

1
2
db.food.insert({"fruit":["apple", "banana", "peach"]});
db.food.find({"fruit" : "banana"});

$all

多个元素匹配数组

db.food.find({"fruit" : {$all: ["apple", "banana"]}}); // 与顺序无关
db.food.find({"fruit" : ["apple", "banana"]}); // 与顺序有关

$size

可以查询指定长度的数组,不能与$addToSet结合使用

db.food.find({"size" : {"$gt" : 3}});

$slice操作符

返回数组的一个子集合

db.blog.posts.findOne(criteria, {"comments" : {"$slice" : 10}}); //返回前10个
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -10}}); //返回后10个
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : [start, limit]}});

查询内嵌文档

有如下文档:

1
2
3
4
5
6
7
{
"name" : {
"first" : "Joe",
"last" : "Schmoe"
},
"age" : 45
}

查询整个文档

db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}}); // 匹配与顺序相关

点表示法查询内嵌的键 (所以插入的文档中不能包含“.”)

db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"});

指定一组条件,要试用”$elemMatch

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

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

$where查询

通过where子句可以借助JavaScript来完成查询:

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,文档就作为结果的一部分被返回;如果返回false,则不返回。

where子查询的缺点

  • 速度上较慢
  • 每个文档都要从BSON转换成JavaScript对象,然后通过”$where“的表达式来运行。
  • 不能使用索引

调优

  • 尽量将常规查询作为前置过滤,与”$where“组合使用可以不牺牲性能
  • 用索引将非”$where“子句进行过滤,”$where“只用于对结果进行调优

游标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var cursor = db.collection.find();
while (cursor.hasNext()) {
obj = cursor.next();
// do stuff
}
// 游标实现了迭代器接口
var cursor = db.people.find();
cursor.forEach(function(x) {
print(lx.name);
});
[output]
adam
matt
zak

数据是惰性加载的,只有执行cursor.hasNext()才会取数据。

limit、skip和sort

limit和skip经常用来分页

skip的参数过大会影响性能=>skip会一条一条跳过数据

比较顺序

  • mongodb处理不同类型的数据是有一个顺序的
  • mongodb对于混合类型的键排序时,顺序是预先定义好的
  1. 最小值
  2. null
  3. 数字(整型、长整型、双精度)
  4. 字符串
  5. 对象、文档
  6. 数组
  7. 二进制数据
  8. 对象id
  9. 布尔型
  10. 日期型
  11. 时间戳
  12. 正则表达式
  13. 最大值

避免使用skip略过大量结果

  1. 最简单的方法就是用limit返回结果的第一页,然后将每个后续页面作为相对开始的偏移量返回

  2. 随机选取文档

高级查询选项

  • $min : document 查询的开始条件
  • $max : document 查询的结束条件
  • $hint : document 指定服务器使用哪个索引进行查询
  • $explain : boolean 获取查询的执行细节,而并非真正执行查询
  • $snapshot : boolean 确保查询的结果是在查询执行那一刻的一致快照

获取一致结果

数据处理过程中将一个文档经过某种变换再存回去。

数据如果超过原文档预留空间的大小,会将其挪到集合末尾处

如此往复,会获取到已经修改的文档。

应对这个问题的方法就是快照$snapshot选项。

游标内幕

服务器端,游标消耗内存和其他资源。游标遍历完结果,或者客户端发来消息终止,数据库会释放这些资源。所以要尽量保证尽快释放游标。

10分钟不使用,游标自动销毁

如果真的希望游标持续时间更长。驱动程序一般都会实现一个叫做immortal的函数或者类似机制,告知数据库不要让游标超时。