(4)详解MongoDB的创建、更新、删除文档
1. 插入
1.1 命令:insert
1.2 举例:
> db.blog.insert({"author":"tian","title":"my first mongodb blog"})
查询结果:
> db.blog.find()
{ "_id" : ObjectId("500bb4b44daafbf976598437"), "author" : "tian", "title" : "my
first mongodb blog" }
1.3 说明:
1)当我们要插入的集合(这里是blog)不存在时,mongodb会在第一次插入式自动创建一个;
2)插入的每一条文档,除了我们制定的键(这里有2个键,author和title),还会自动增加一个_id键,相当于关系型数据库的主键,如果我们没有指定的话。
该键对于一个集合必须是唯一的,它可以使任意类型,默认是ObjectId对象。
由于mongodb一开始设计就是用来作为分布式数据库的,因此没有采用自增长的方式来创建_id键,因为在不同的服务器上同步自增长主键费时又费力。
关于ObjectId,更多可以参考:http://www.mongodb.org/display/DOCS/Object+IDs
当然我们也可以自己指定:
> db.blog.insert({"_id":2012,"author":"tian","title":"my 2nd mongodb blog"}) > db.blog.find() { "_id" : ObjectId("500bb4b44daafbf976598437"), "author" : "tian", "title" : "my first mongodb blog" } { "_id" : 2012, "author" : "tian", "title" : "my 2nd mongodb blog" }
1.4 插入原理:
当我们将数据插入到mongodb数据库时,数据会被转换成BSON的形式(BSON是mongodb存储数据的形式,类似JSON的,是轻量的二进制格式,
能将mongodb的所有文档表示为字节字符串,数据库能理解BSON,存在磁盘上的文档也是BSON格式,更多请参考: http://www.bsonspec.org/),
然后存入数据库。
在这个阶段,mongodb只检查2件事:
1)是否包含_id键;
2)文档是否超过16M,注意,这里的大小是转成BSON格式以后的大小,可以通过Object.bsonsize(your-object)查看大小;(以mongodb 1.8为准)
只要这两点满足,就会将文档原本的存入到数据库
这样做有好处也有副作用,副作用就是可以插入无效的数据,好处就是可以使数据库更安全,远离注入式攻击,因为插入不执行代码。
2. 删除
2.1 命令:remove
2.2 举例:
> db.blog.remove({"title":"my first mongodb blog"})
> db.blog.remove()
2.3 说明:
1) 在上面的2个例子中,第一个例子的remove接受一个参数,用于限定要删除的文档,第二个例子没有指定参数,注意,这种操作是很危险的,因为它将删除整个集合里的文档,但是集合以及索引会被保留,第二个例子等价于db.blog.remove({})
2) 删除是永久性的,不能撤销,也不能恢复
3) 删除文档的速度相当快,如果要删除整个集合里的文档,可以采用db.drop_collection(your-collection),然后重建集合和索引,该方法速度非常快,唯一的缺点是整个集合都被删除了,包括索引
4) 根据_id键来删除文档的效率是最高的
5) 考虑一种比较极端,或者高并发可能发生的情况,当你要删除一个集合里的文档时,刚好有另一个进程在update其中的文档,在这种情况下,正被update的文档是不会被删除的,如果这并不是你想要的,可以通过制定参数$atomic参数为true来删除所有满足条件的文档,如:db.blog.remove({"author":"tian",$atomic:true}),当然,这样做也是有副作用的,就是当我们执行remove操作的时候,将阻止其他操作。
3. 更新
3.1 命令:update
3.2 举例:
> var mypost = db.blog.findOne({"_id":ObjectId("500bc4304daafbf976598439")}) > mypost.author="tian.chen" tian.chen > mypost { "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title" : "my first mongodb blog" } > db.blog.update({"_id":ObjectId("500bc4304daafbf976598439")},mypost) > db.blog.findOne({"_id":ObjectId("500bc4304daafbf976598439")}) { "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title" : "my first mongodb blog" }
3.3 说明:
1) 我们首先通过findOne来获取一个文档,并赋值给mypost,然后,修改mypost的author键,最后再通过db.blog.update更新文档
2) 更新时匹配多个文档,更新的时候,由于第二个参数的存在就会产生重复的_id键,就会报错。怎么说呢,举个例子
> db.blog.find() { "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title" : "my first mongodb blog", "age" : 28 } { "_id" : 2012, "author" : "tian.chen", "title" : "my 2nd mongodb blog", "age" : 6 } > var tianpost=db.blog.findOne({"author":"tian.chen","age":6}) > tianpost { "_id" : 2012, "author" : "tian.chen", "title" : "my 2nd mongodb blog", "age" : 6 } > tianpost.age=26 26 > db.blog.update({"author":"tian.chen"},tianpost) cannot change _id of a document old:{ _id: ObjectId('500bc4304daafbf976598439'), author: "tian.chen", title: "my first mongodb blog", age: 28.0 } new:{ _id: 201 2.0, author: "tian.chen", title: "my 2nd mongodb blog", age: 26.0 }
在这个例子中,我们的post集合里包含2个文档,我们要将第二个文档的age改成26,首先通过author和age获取一个文档并赋值给tianpost,在这里,tianpost也是具有_id键的,其值为2012,然后,我们将tianpost的age改成26后,然后通过author=tian.chen查找文档,将其update为tianpost,问题就出现在这里,通过author=tian.chen查找文档时,首先找到第一个文档,其_id为 ObjectId('500bc4304daafbf976598439'),而tianpost._id=2012,我们知道_id是不能修改的,结果就报错了!
为了避免这种情况,最好确保更新总是指定唯一文档。
3.4 使用修改器
更新修改是种特殊的键,用来指定复杂的操作,如调整,增加或删除键,还可能操作数据或内嵌的文档。
假设有这样一个文档:
{
"_id" : 2012,
"age" : 26,
"author" : "tian.chen",
"pageviews" : 1,
"title" : "my 2nd mongodb blog"
}
其中的键pageviews表示该post被阅读的次数,每阅读一次就增加1,这时,我们可以使用$inc修改器
> db.blog.update({"_id":2012},{"$inc":{"pageviews":1}})
3.4.1 $set修改器
$set修改器用来指定一个键的值,如果这关键不存在就创建它,这对于修改来说是很方便的,因为我们通常修改的只是及个别的键的值的。
假设我们要修改下面文档的age:
{
"_id" : 2012,
"age" : 26,
"author" : "tian.chen",
"pageviews" : 1,
"title" : "my 2nd mongodb blog"
}
如果我们只是简单的用db.blog.update({"_id":2012},{"age":28}),那么,将会用{"age":28}替换掉整个文档,这个时候,$set修改器就很有用处了:
db.blog.update({"_id":2012},{"$set":{"age":28}})
当然,也可以修改多个:
db.blog.update({"_id":2012},{"$set":{"age":28,"author":"tian"}})
用$set可以修改键的值,如果键不存在就创建它,与之对应的,用$unset可以删除键,如:
db.blog.update({"_id":2012},{"$unset":{"pageviews":1}})
这样,就会将文档中的pageviews键删除掉
3.4.2 $inc修改器
正如我们在上面看到的,$inc修改器用来增加键的值.
需要注意的是$inc只能应用于整数、长整数、双精度浮点数,同时$inc的键的值必须为数字,如果要修改其他类型,应使用上面的$set
3.4.3 数组修改器 $push & $pop
如果指定的键存在,$push就会向已有的数据末尾加入一个元素,如果没有,则会创建一个新的数组
如:
>db.blog.update({"_id":2012},{$push:{"comments":{"name":"jake","content":"nice post"}}})
查出来的结果为:
> db.blog.find()
{ "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title"
: "my first mongodb blog", "age" : 28 }
{ "_id" : 2012, "age" : 28, "author" : "tian", "comments" : [ { "name" : "jake",
"content" : "nice post" } ], "title" : "my 2nd mongodb blog" }
有时候,我们可能会希望,如果一个值在数组中不存在,就添加进去,可以用如下方式来实现,即通过$ne来实现:
> db.papers.insert({"title":"mongodb post","authors":["tian"]})
> db.papers.update({"authors":{"$ne":"harry"}},{$push:{"authors":"harry"}})
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry" ],
"title" : "mongodb post" }
也可用$addToSet来实现,因为$ne并不总是可行:
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {"$addToSet":{"authors":"jerry"}})
>{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "
jerry" ], "title" : "mongodb post" }
$addToSet还有一个妙处,通过与$each结合,可以插入多个值,当然,如果值已经在数组中,就不会被添加进去:
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {"$addToSet":
... {"authors":{"$each":["tian","jerry","mike"]}}})
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "
jerry", "mike" ], "title" : "mongodb post" }
"tian","jerry"已经存在,因此没有重复添加,"mike"不存在,被添加进来了。
删除数组的元素可以用$pop或$pull
$pop主要用于删除数组头部或尾部的值{$pop:{key:-1}}和{$pop:{key:1}}
> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "
jerry", "mike" ], "title" : "mongodb post" }
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {$pop:{"authors":1}}
... )
> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "
jerry" ], "title" : "mongodb post" }
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {$pop:{"authors":-1}})
> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "harry", "jerry" ]
, "title" : "mongodb post" }
可以发现{$pop:{key:-1}}删除尾部,而{$pop:{key:1}}删除头部的。
更经常的,我们希望通过值来判断,这时候就可以用$pull,{"$pull":{key:value}}
3.5 upsert更新
upsert是一种特殊的更新,要是没有符合更新条件的文档,就会以这个条件和文档为基础创建一份新的文档。如果有匹配的文档,则正常更新。
假设我们有一个集合analytics,用来记录每个url的访问次数,每访问一次就给pageviews键加1,正常情况,我们需要判断当前访问的url有没有存在,如果没有,则添加,有则更新。
采用upsert,我们可以有更优雅的写法:
db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)
在这里,我们通过给update传递第三个参数表示upsert
3.6 save函数
save跟upsert有点类似,也是不存在时插入,存在时更新。不同的是save只有一个参数。看下面的例子:
> var x = db.blog.findOne({"_id":2012})
> x.age=99
99
> db.blog.save(x)
> db.blog.find({"_id":2012})
{ "_id" : 2012, "age" : 99, "author" : "tian", "comments" : [ { "name" :
"jake", "content" : "nice post" }, { "name" : "tina",
"content" : "not too bad" } ], "title" : "my 2nd mongodb blog" }
3.7 更新多个文档
在前面的例子中,我们都是只更新一个文档,mongodb目前也是默认只更新一个文档。我们可以通过对update指定第四个参数,来更新多个文档
在上面的例子中,我们对比了没传第四个参数和传第四个参数进行更新的区别,传第四个参数后,符合条件的都会更新。
更新完成后,可以通过db.runCommand({getLastError:1})来获取更新了多少文档。
参考:MongoDB权威指南