3.1 插入并保存文档
插入是向MongoDB中添加数据的基本方法。对目标集使用insert方法,插入一个文档
db.foo.insert({"bar":"baz"})
这个操作会给文档增加一个"_id"键(要是原来没有的话),然后将其保存到MongoDB中
3.1.1 批量插入
批量插入能传递一个由文档构成的数组给数据库
一次批量插入只是单个的TCP请求,也就是说避免了许多零碎的请求所带来的开销。
只有插入多个文档到一个集合的时候,这种方式才会有用,而不能用批量插入一次对多个集合执行操作。要是只是导入原始数据(例如,从数据feed或者MySql中导入),可以使用命令行工具,如mogoimport,而不是使用批量插入。另一方面,可以用它在存入MongoDB之前对数据做一些小的修整(转换日期成为日期类型,或添加自定义的"_id"),所以批量插入对导入数据来说也是有用的。
当前版本的MongoDB消息长度最大是16M,所以使用批量插入时还是有限制的。
当执行插入的时候,使用的驱动程序会将数据转换成BSON的形式,然后将其送入数据库。数据库解析BSON,检查是否包含“_id”键并且文档不超过4M,除此之外,不做别的数据验证,就只是简单地将文档原样存入数据库中。
所有主流语言(也包括绝大部分非主流语言)的驱动会在传送数据之前进行一些数据的有效性检查(文档是否超长,是否包含非UTF-8字符,或者使用了位置类型)。要是对使用的驱动拿捏不准,可以再启动数据库服务器的时候使用--objectcheck选项,这样服务器就会在插入之前检查文档结构的有效性(当然这么做要牺牲些性能)
3.2 删除文档
现在数据库中有些数据,要删除它:
db.users.remove()
上述命令会删除users集合中所有的文档。但不会删除集合本身,原有的索引也会保留。remove函数可以接受一个查询文档作为可选参数。给定这个参数以后,只有符合条件的文档才被删除。例如,假设要删除mailing.list集合中所有optout为true的人:
db.mailing.list.remove({"opt-out":true})
删除是永久性的,不能撤销,也不能恢复。
删除速度
删除文档通常会很快,但是要清除整个集合,直接删除集合(然后重新建索引)会更快。
3.3 更新文档
文档存入数据库以后,就可以使用update方法来修改它。update有两个参数,一个是查询文档,用来找出要更新的文档,另一个是修改器(modifier文档),描述对找到的文档做哪些更改。
更新操作是原子的。
3.3.1 文档替换
[root@localhost bin]# ./mongo
MongoDB shell version: 2.0.2
connecting to: test
> db
test
> db.users.insert({"name":"joe","friend":32,"enemies":23})
> db.users.find()
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "name" : "joe", "friend" : 32, "enemies" : 23 }
> db.users.findOne()
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"name" : "joe",
"friend" : 32,
"enemies" : 23
}
> var joe = db.users.findOne({"name":"joe"})
> joe.relationships={"friends":joe.friends,"enemies":joe.enemies};
{ "friends" : undefined, "enemies" : 23 }
> joe.relationships={"friends":joe.friend,"enemies":joe.enemies};
{ "friends" : 32, "enemies" : 23 }
> joe.username=joe.name;
joe
> delete joe.friends;
true
> delete joe.enemies;
true
> delete joe.name;
true
> db.users.update({"name":"joe"},joe)
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"friend" : 32,
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> delete joe.friend;
true
> db.users.update({"name":"joe"},joe)
> var joe = db.users.findOne({"name":"joe"})
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"friend" : 32,
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> var joe = db.users.findOne({"name":"joe"})
> delete joe.friend;
Mon Aug 27 13:48:57 TypeError: joe has no properties (shell):1
> db.users.update({"name":"joe"},joe)
assert failed : need an object
Error("Printing Stack Trace")@:0
()@shell/utils.js:35
("assert failed : need an object")@shell/utils.js:46
(null,"need an object")@shell/utils.js:54
([object Object],null)@shell/collection.js:189
@(shell):1
Mon Aug 27 13:49:25 uncaught exception: assert failed : need an object
> db.users.update({"name":"joe"},joe);
assert failed : need an object
Error("Printing Stack Trace")@:0
()@shell/utils.js:35
("assert failed : need an object")@shell/utils.js:46
(null,"need an object")@shell/utils.js:54
([object Object],null)@shell/collection.js:189
@(shell):1
Mon Aug 27 13:49:46 uncaught exception: assert failed : need an object
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"friend" : 32,
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> var joe = db.users.findOne({"name":"joe"})
> joe.friend
Mon Aug 27 13:50:15 TypeError: joe has no properties (shell):1
> var joe = db.users.findOne({"username":"joe"})
> delete joe.friend
true
> db.users.update({"username":"joe"},joe);
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
>
上面操作失败是因为在操作的过程中已将name属性改为了username属性,当find查询时,按照原有条件已经无法查询出对应的记录,故在更新的过程中,会出现错误提示。
常见错误就是查询条件匹配了多个文档,然后更新的时候由于第二个参数的存在就产生了重复的"_id"值。数据库会报错,不做任何修改。
译注:除了shell外,一般程序是不会报错的,除非用getLastError
例如,有好几个文档都有相同的"name",但我们没有意识到:
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
> db.users.insert({"name":"joe1","age":65});
> db.users.insert({"name":"joe1","age":20});
> db.users.insert({"name":"joe1","age":49});
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
现在如果第二个joe1过生日,要增加“age”的值,可能会这么做:
> var joe1=db.people.findOne({"name":"joe","age":20});
> joe1.age++;
Mon Aug 27 14:04:02 TypeError: joe1 has no properties (shell):1
> var joe1=db.people.findOne({"name":"joe1","age":20});
> joe1.age++;
Mon Aug 27 14:04:17 TypeError: joe1 has no properties (shell):1
> db.people.findOne({"name":"joe1","age":20});
null
> db.people.findOne({"name":"joe1"});
null
> db.users.findOne({"name":"joe1","age":20});
{
"_id" : ObjectId("503b0d78a53a93158463f030"),
"name" : "joe1",
"age" : 20
}
> var joe1=db.users.findOne({"name":"joe1","age":20});
> joe1.age++;
20
> db.users.update({"name":"joe1"},joe1);
cannot change _id of a document old:{ _id: ObjectId('503b0d70a53a93158463f02f'), name: "joe1", age: 65.0 } new:{ _id: ObjectId('503b0d78a53a93158463f030'), name: "joe1", age: 21.0 }
>
当调用update时,数据库会查找一个与{"name":"joe1"}匹配的文档。
找到的第一个就是那个65岁的joe1.然后数据库试着用变量joe中的内容替换找到的文档,但是会发现集合里面已经有一个具有同样"_id"的文档。所以更新就会失败,因为"_id"值必须唯一。为了避免这种情况,最好确保更新总是指定唯一文档,例如通过像“_id”这样的键来匹配。
3.3.2 使用修改器
通常文档只会有一部分要更新。利用原子的更新修改器,可以使得这种部分更新极为高效。更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整、增加或者删除键,还可能是操作数组或者内嵌文档。
假设要在一个集合中放置网站的分析数据,每当有人访问页面的时候,就要增加计数器。可以使用更新修改器原子性地完成这个增加。每个URL及对应的访问次数都以如下的方式存储在文档中。
> db.analytics.insert({"url":"www.example.com","pageviews":52});
每次有人访问页面,就通过URL找到该页面,并用"$inc"修改器增加"pageviews"的值。
> db.analytics.update({"url":"www.example.com"},
... {"$inc":{"pageviews":1}})
接着,执行一个find操作,会发现"pageviews"的值增加了1
> db.analytics.find();
{ "_id" : ObjectId("503b116fa53a93158463f032"), "url" : "www.example.com", "pageviews" : 53 }
>
使用修改器时,"_id"的值不能改变。(注意:整个文档替换时可以改变"_id"的)其他键值,包括其他唯一索引的值,都是可以更改的。
1 “$set” 修改器入门
"$set"用来指定一个键的键值,如果这个键不存在,则创建它。这对更新模式或者增加用户定义键来说非常方便。例如,用户资料存储在下面这样的文档里:
> db.analytics.find();
{ "_id" : ObjectId("503b116fa53a93158463f032"), "url" : "www.example.com", "pageviews" : 53 }
> db.users.insert({"name":"joe","age":30,"sex":"male","location":"Wisconsin"})
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "name" : "joe", "age" : 30, "sex" : "male", "location" : "Wisconsin" }
> db.users.update({"_id":ObjectId("503b338da53a93158463f033")},{"$set":{"favorite book":"war and peace"}})
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "favorite book" : "war and peace", "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
要是用户喜欢另外一本书,“$set”又能帮上忙了:
> db.users.update({"name":"joe"},{"$set":{"favorite book":"green eggs and ham"}});
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "favorite book" : "green eggs and ham", "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
用"$set"甚至可以修改键的数据类型。例如,如果用户又觉得喜欢的是一堆书,就可以将"favorite book"键的值变成一个数组
> db.users.update({"name":"joe"},{"$set":{"favorite book":["cat's cradle","foundation trilogy","ender's game"]}});
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "favorite book" : [ "cat's cradle", "foundation trilogy", "ender's game" ], "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
如果用户突然发现自己不爱读书,可以用“$unset“将键完全删除:
> db.users.update({"name":"joe"},{"$unset":{"favorite book":1}});
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
>
现在这个文档就和例子开始的时候一样了。
也可以用"$set"修改内嵌文档:
> db.blog.posts.insert({"title":"A Blog Post","content":"...","author":{"name":"joe","email":"joe@example.com"}});
> db.blog.posts.findOne();
{
"_id" : ObjectId("503b3d9109b5fcd77ef4a2a0"),
"title" : "A Blog Post",
"content" : "...",
"author" : {
"name" : "joe",
"email" : "joe@example.com"
}
}
修改后
> db.blog.posts.update({"author.name":"joe"},{"$set":{"author.name":"joe schmoe"}});
> db.blog.posts.findOne();
{
"_id" : ObjectId("503b3d9109b5fcd77ef4a2a0"),
"author" : {
"email" : "joe@example.com",
"name" : "joe schmoe"
},
"content" : "...",
"title" : "A Blog Post"
}
>
增加、修改或删除键的时候,应该使用$修改器。要把"foo"的值设置为"bar",常见的错误做法如下:
db.coll.update(criteria,{"foo":"bar"})
这会事与愿违。实际上这会将整个文档用{"foo":"bar"}替换掉。一定要使用以$开头的修改器来修改键/值对
2、增加或减少
"$inc"修改器用来增加已有键的值,或者在键不存在时创建一个键。对于分析数据、因果关系、投票或者其他有变化数值的地方,使用这个都会非常方便。
假如建立了一个游戏集合,将游戏和变化的分数都存储在里面。比如用户玩弹珠游戏,可以插入一个包含游戏名和玩家的文档来标识不同的游戏:
> db.games.insert({"game":"pinball","user":"joe"})
> db.games.findOne();
{
"_id" : ObjectId("503b410bd2f71a75825a1b59"),
"game" : "pinball",
"user" : "joe"
}
要是小球撞到了砖块,就会给玩家加分。分数可以随便给,这里就把玩家得分基数约定成50就好了。使用"$inc"修改器给玩家加50分
> db.games.update({"game":"pinball","user":"joe"},{"$inc":{"score":50}})
> db.games.findOne();
{
"_id" : ObjectId("503b410bd2f71a75825a1b59"),
"game" : "pinball",
"score" : 50,
"user" : "joe"
}
分数键(score)原来并不存在,所以"$inc"创建了这个键,并把值设定成增加量:50.
如果小球落入加分区,要加10000分。只要给"$inc"传递一个不同的值就好了:
> db.games.update({"game":"pinball","user":"joe"},{"$inc":{"score":10000}});
> db.games.find();
{ "_id" : ObjectId("503b410bd2f71a75825a1b59"), "game" : "pinball", "score" : 10050, "user" : "joe" }
”score“键存在并有数字类型的值,所有服务器就把这个值增加了10 000
”$inc“与"$set"的用法类似,就是专门来增加(和减少)数字的。"$inc"只能用于整数、长整数或双精度浮点数。要是用在其他类型的数据上就会导致操作失败。其中包括很多语言会自动转换成数字的类型,例如null、布尔类型或数字构成的字符串。
另外,”$inc“键的值必须为数字。不能使用字符串、数组或其他非数字的值。
3 数组修改器
有一类很好的修改器可用于操作数组。数组是常用且非常有用的数据结构:它们不仅是可通过索引进行引用的列表,而且还可以作为集合来用。
数组操作,顾名思义,只能用在值为数组的键上。例如不能对整数做push,也不能对字符串做pop。使用"$set"或"$inc"来修改标量值。
如果指定的键已经存在,"$push"会向已有的数组末尾加入一个元素,要是没有就会创建一个新的数组。
例如,假设要存储博客文章,要添加一个包含一个数组的"comments"(评论)键。可以向还不存在的"comments"数组push一个评论,这个数组会被自动创建,并加入评论:
> db.blog.posts.update({"title":"A blog post"},{"$push":{"comments":{"name":"joe","email":"joe@example.com","content":"nice post."}}});
> db.blog.posts.findOne({"_id":ObjectId("503b54efd2f71a75825a1b5a")});
{
"_id" : ObjectId("503b54efd2f71a75825a1b5a"),
"comments" : [
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
}
],
"content" : "...",
"title" : "A blog post"
}
>
要是还想添加一条评论,可以接着使用"$push"
> db.blog.posts.update({"title":"A blog post"},{$push:{"comments":{"name":"joe1","email":"joe1@example.com","content":"nice post."}}});
> db.blog.posts.findOne({"_id":ObjectId("503b54efd2f71a75825a1b5a")});
{
"_id" : ObjectId("503b54efd2f71a75825a1b5a"),
"comments" : [
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe",
"email" : "joe@example.com",
"content" : "nice post."
},
{
"name" : "joe1",
"email" : "joe1@example.com",
"content" : "nice post."
}
],
"content" : "...",
"title" : "A blog post"
}
>
经常会有这种情况,如果一个值不在数组里面就把它加进去。可以再查询文档中用"$ne"来实现。例如,要是作者不在引文列表中就添加进去,可以这么做:
> db.papers.update({"authors cited":{"$ne":"Richie"}},{$push:{"authors cited":"Richie"}});
要知道有些情况"$ne"根本行不通,有些时候更适合用"addToSet"
例如,有一个表示拥护的文档,已经有了电子邮件地址信息:
> db.users.insert({"username":"joe","emails":["joe@example","joe@gmail.com","joe@yahoo.com"]});
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
>
当添加新的地址时,用”$addToSet“可以避免重复:
> db.users.insert({"username":"joe","emails":["joe@example","joe@gmail.com","joe@yahoo.com"]});
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> db.users.update({"_id":ObjectId("503b08dea53a93158463f02e")},{"$addToSet":{"emails":"joe@gmail.com"}})
> db.users.findOne();
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"emails" : [
"joe@gmail.com"
],
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> db.users.find({"_id":ObjectId("503b08dea53a93158463f02e")});
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "emails" : [ "joe@gmail.com" ], "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
> db.users.findOne({"_id":ObjectId("503b08dea53a93158463f02e")});
{
"_id" : ObjectId("503b08dea53a93158463f02e"),
"emails" : [
"joe@gmail.com"
],
"relationships" : {
"friends" : 32,
"enemies" : 23
},
"username" : "joe"
}
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "emails" : [ "joe@gmail.com" ], "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
{ "_id" : ObjectId("503b5937d2f71a75825a1b5b"), "username" : "joe", "emails" : [ "joe@example", "joe@gmail.com", "joe@yahoo.com" ] }
> db.users.update({"_id":ObjectId("503b5937d2f71a75825a1b5b")},{"$addToSet":{"emails":"joe@gmail.com"}})
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "emails" : [ "joe@gmail.com" ], "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
{ "_id" : ObjectId("503b5937d2f71a75825a1b5b"), "username" : "joe", "emails" : [ "joe@example", "joe@gmail.com", "joe@yahoo.com" ] }
>
> db.users.update({"_id":ObjectId("503b5937d2f71a75825a1b5b")},{"$addToSet":{"emails":"joe@hotmail.com"}})
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "emails" : [ "joe@gmail.com" ], "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
{ "_id" : ObjectId("503b5937d2f71a75825a1b5b"), "emails" : [ "joe@example", "joe@gmail.com", "joe@yahoo.com", "joe@hotmail.com" ], "username" : "joe" }
>
将"$addToSet"和"$each"组合起来,可以添加多个不同的值,而用”$ne“和"$push"组合就不能实现。例如,想一次添加多个邮件地址,就可以使用这些修改器:
> db.users.update({"_id":ObjectId("503b5937d2f71a75825a1b5b")},{"$addToSet":{"emails":{"$each":["joe@php.net","joe@example.com","joe@python.org"]}}});
> db.users.find();
{ "_id" : ObjectId("503b08dea53a93158463f02e"), "emails" : [ "joe@gmail.com" ], "relationships" : { "friends" : 32, "enemies" : 23 }, "username" : "joe" }
{ "_id" : ObjectId("503b0d70a53a93158463f02f"), "name" : "joe1", "age" : 65 }
{ "_id" : ObjectId("503b0d78a53a93158463f030"), "name" : "joe1", "age" : 20 }
{ "_id" : ObjectId("503b0d7ca53a93158463f031"), "name" : "joe1", "age" : 49 }
{ "_id" : ObjectId("503b338da53a93158463f033"), "age" : 30, "location" : "Wisconsin", "name" : "joe", "sex" : "male" }
{ "_id" : ObjectId("503b5937d2f71a75825a1b5b"), "emails" : [ "joe@example", "joe@gmail.com", "joe@yahoo.com", "joe@hotmail.com", "joe@php.net", "joe@example.com", "joe@python.org" ], "username" : "joe" }
>
有几个从数组中删除元素的方法。若是把数组看成队列或者栈,可以用"$pop",这个修改器可以从数组任何一端删除元素{$pop:{key:1}}从数组末尾删除一个元素,{$pop:{key:-1}}则从头部删除。
有时需要基于特定的条件来删除元素,而不仅仅是依据位置,"$pull"可以做到。例如,有个待完成事项列表,顺序有些问题:
> db.lists.find();
{ "_id" : ObjectId("503b5ed8d2f71a75825a1b5c"), "todo" : [ "dishes", "laundry", "dry cleaning" ] }
要是想把洗衣服(laundry)放到第一位,可以从列表中先删掉
> db.lists.update({},{"$pull":{"todo":"laundry"}})
> db.lists.find();
{ "_id" : ObjectId("503b5ed8d2f71a75825a1b5c"), "todo" : [ "dishes", "dry cleaning" ] }
”$pull“会将所有匹配的部分删掉。对数组[1,1,2,1]执行pull 1,得到结果就是只有一个元素的数组[2]
4.数组的定位修改器
若是数组有多个值,而我们只想对其中的一部分进行操作,这就需要一些技巧。有两种方法操作数组中的值:通过位置或者定位操作符(”$“).
数组都是以0开头的,可以将下标直接作为键来选择元素。例如,这里有个文档,其中包含由内嵌文档组成的数组,比如包含评论的博客文章。
定位符只更新一个匹配的元素。所以,如果John有不止一个评论,那么只有他的第一条评论中的名字会被更改。
5 修改器速度
有的修改器运行比较快。$inc能就地修改,因为不需要改变文档的大小,只需要将键的值修改一下,所以非常快。而数组修改器可能更改了文档的大小,就会慢一些("$set"能在文档大小不发生变化时立即修改,否则性能也会有所下降)。
MongoDB预留了些补白给文档,来适应大小变化(事实上,系统会根据文档通常的大小变化情况来相应地调整补白的大小),但是要是超过了原来的空间,最后还是要分配一块新的空间。空间分配除了会减慢速度,同时会随着数组变长,MongoDB需要更长的时间来遍历整个数组,对每个数组的修改也会慢下来。
要是”$push“成为瓶颈,可以将内嵌数组独立出来,放到单独一个集合里面。
3.3.3 upsert
upsert是一种特殊的更新。要是没有文档符合更新条件,就会以这个条件和更新文档为基础创建一个新的文档。如果找了匹配的文档,则正常更新。upsert非常方便,不必预置集合,同一套代码可以既创建又更新文档。
让我们回过头看看那个记录网站页面访问次数的例子。要是没有upsert,就得试着查询URL,没有找到就得新建一个文档,找到的话就增加访问次数。要是把这个写成JavaScript程序(而不是用mongo scriptname.js来运行的一系列shell命令),会是如下这样的:
//check if we have an entry for this page
blog = db.analytics.findOne({url:"/blog"})
//if we do ,add one to the number of views and save
if(blog){
blog.pageviews++;
db.analytics.save(blog);
//otherwise,create a new document for this page
}else{
db.analytics.save({url:"/blog",pageviews:1})
}
这就是说如果有人访问页面,我们得去数据库打个来回,然后选择更新或插入。要是多个进程同时运行这段代码,还得考虑对于给定URL不能同时插入文档的限制。
要是使用upsert,既可以避免竟态问题,又可以缩减代码量(update的第三个参数表示这是个upsert):
db.analytics.update({"url":"/blog"},{"$inc":{"visits":1}},true)
这行代码和之前的代码作用一样,但它更高效,并且是原子性的!创建新文档会将条件文档作为基础,然后将修改器文档应用于其上。例如,要是执行一个匹配键并增加对应键值的upsert操作,会在匹配的基础上进行增加:
> db.math
test.math
> db.math.find();
> db.math.remove();
> db.math.update({"count":25},{"$inc":{"count":3}},true)
> db.math.findOne();
{ "_id" : ObjectId("503b8a6dadee6f8d9eb7cf77"), "count" : 28 }
先是remove清空了集合,里面就没有文档了,upsert创建一个键"count"的值为25的文档,然后将这个值加3,最后得到"count"为28的文档。要是不开启upsert选项,{"count":25}不会匹配到任何文档,也就是没有任何更改。
要是将这个upsert(条件为{count:25})再次运行,还会创建一个新文档。这是因为没有文档满足匹配条件(唯一的文档的"count"的值是28)
sase shell 帮助程序
save是一个shell函数,可以在文档不存在时插入,存在时更新。它只有一个参数文档。要是这个文档含有"_id"键,save会调用upsert。否则。会调用插入。程序员可以非常方便的使用这个函数在shell中快速修改文档。
var x = db.foo.findOne();
x.num = 42
db.foo.save(x)
要是不用save,最后一行可以像下面这样写,但很罗嗦:
db.foo.update({"_id":x._id},x)
3.3.4 更新多个文档
默认情况下,更新只能对符合匹配条件的第一个文档执行操作。要是有多个文档符合条件,其余的文档就没有变化。要使所有匹配到的文档都得到更新,可以设置update的第4个参数为true。
多文档更新对模式迁移非常有用,还可以在对特定用户发布新功能的时候使用。例如,假设要给所有在特定日期过生日的用户一份礼物,就可以使用多文档更新,将"gift"增加到他们的账号
db.users.update({birthday:"10/13/1978"},{$set:{gift:"Happy Birthday!"}},false,true)
这样就给生日为1978年10月13日的所有用户文档增加了"gift"键。
想要知道多文档更新到底更新了多少文档,可以运行getLastError命令(或许叫做"getLastOpStatus"更为合适)。键"n"的值就是要的数字。
db.count.update({x:1},{$inc:{x:1}},false,true)
db.runCommand({getLastError:1})
{
"err" : null,
"updatedExisting":true,
"n":5,
"ok":true
}
这里"n"为5,说明有5个文档被更新了。"updatedExisting"为true,说明是对已有的文档进行更新。