Redis采用的是异步I/O非阻塞的单进程模型,每一条Redis命令都是原子性的。
那么mongoDB呢? mongo有哪些原子操作呢?有哪些实现事务性操作的技巧呢?
MongoDB 原子操作
mongodb不支持事务,所以,在你的项目中应用时,要注意这点。无论什么设计,都不要要求mongodb保证数据的完整性。
但是mongodb提供了许多原子操作,比如文档的保存,修改,删除等,都是原子操作。
所谓原子操作就是要么这个文档保存到Mongodb,要么没有保存到Mongodb,不会出现查询到的文档没有保存完整的情况。
原子操作数据模型
- 考虑下面的例子,图书馆的书籍及结账信息。
- 实例说明了在一个相同的文档中如何确保嵌入字段关联原子操作(update:更新)的字段是同步的。
book = {
_id: 123456789,
title: "MongoDB: The Definitive Guide",
author: [ "Kristina Chodorow", "Mike Dirolf" ],
published_date: ISODate("2010-09-24"),
pages: 216,
language: "English",
publisher_id: "oreilly",
available: 3,
checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]
}
- 你可以使用 db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。
- 在同一个文档中嵌入的 available 和 checkout 字段来确保这些字段是同步更新的:
db.books.findAndModify ( {
query: {
_id: 123456789,
available: { $gt: 0 }
},
update: {
$inc: { available: -1 },
$push: { checkout: { by: "abc", date: newDate() } }
}
} )
拥有类似事务特性的更新与查询操作——findAndModify.
它是原子性的,会返回符合查询条件的更新后的文档。
db.COLLECTION_NAME.findAndModify({query:{},
update:{},
remove:true|false,
new:true|false,
sort:{},
fields:{},
upsert:true|false});
- query是查询选择器,与findOne的查询选择器相同
- update是要更新的值,不能与remove同时出现
- remove表示删除符合query条件的文档,不能与update同时出现
- new为true:返回个性后的文档,false:返回个性前的,默认是false
- sort:排序条件,与sort函数的参数一致。
- fields:投影操作,与find*的第二个参数一致。
- upsert:与update的upsert参数一样。
不论是update的第二个参数,还是findAndModify的update,在不指定更新操作符的情况下,将会用指定的新值替换旧值。
比如,
use iteye;
db.blog.update({_id:ObjectId('......')},{title:'new title'});
//上面的操作就把指定_id的文档的标题改成了‘new title’
如果指定了更新操作符,就可以实现更复杂灵活的更新操作。
可以通过更新操作符,增加或减少数值,针对数组类型的属性,做类似队列或栈的操作。
单从这一点来说,mongo要比sql数据库强大的多了。
原子操作常用命令(更新操作符)
- $set
- 用来指定一个键并更新键值,若键不存在并创建。
用"$set"甚至可以修改键的数据类型
db.users.insert({"name":"egger", "age": 28, "sex" : "male"})
db.users.update({"_id" : ObjectId("51826852c75fdd1d8b805801")},{"$set" : {"sex" :1 }} )
{ $set : { field : value } }
使用"$set"修改内嵌文档:
db.posts.update({"author.name":"egger"},{"$set":{"author.name":"mongo","author.age":18}})
- $unset
- 用来删除一个键。
{ $unset : { field : 1} }
- $inc
- $inc可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作。
负数,表示减少
"$inc"只能用于整数、长整数或双精度浮点数。要是用在其他类型的数据上就会导致操作失败。
{ $inc : { field : value } }
- upsert
upsert是一种特殊的更新操作,不是一个操作符。(upsert = up[date]+[in]sert)
update() 方法的三个参数是upsert,这个参数是个布尔类型,默认是false。
当它为true的时候,update方法会首先查找与第一个参数匹配的记录,在用第二个参数更新之,
如果找不到与第一个参数匹配的的记录,
就会以这个条件和更新文档为基础创建一个新的文档。如果找到了匹配的文档,则正常更新。
upsert非常方便,不必预置集合,同一方法可以既创建又更新文档。
- $setOnInsert
当update方法使用upsert选项执行insert操作时,$setOnInsert操作符给相应的字段赋值。类似sql中update 语句的set。
db.collection.update( <query>,
{ $setOnInsert: { <field1>: <value1>, ... } },
{ upsert: true } //{ upsert: true }可以用true替换
)
- $(query)
$ (query)
语法: { "<array>.$" : value }
当对数组字段进行更新时,且没有明确指定的元素在数组中的位置,
我们使用定位操作符("$")标识一个元素,数字都是以0开始的。
和update()一起使用:
定位操作符("$")作为第一个匹配查询条件的元素的占位符,也就是在数组中的索引值。
数组字段必须出现查询文档中。
集合students中有两条文档:
{ "_id" : 1, "grades" : [ 78, 88, 88 ] }
{ "_id" : 2, "grades" : [ 88, 90, 92 ] }
执行下列语句创建集合文档数据:
db.students.remove();
db.students.insert({ "_id" : 1, "grades" : [ 78, 88, 88 ] });
db.students.insert({ "_id" : 2, "grades" : [ 88, 90, 92 ] });
执行下列操作:
//查询匹配的文档中,数组有2个88,只更新第一个匹配的元素,也就是"grades.1"
db.students.update( { _id: 1, grades: 88 }, { $set: { "grades.$" : 82 } }) ;
//查询文档中没有出现grades字段,查询报错
db.students.update( { _id: 2 }, { $set: { "grades.$" : 82 } } );
- $push
- 用法:
{ $push : { field : value } }
把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。
- $pushAll
- 同$push,只是一次可以追加多个值到一个数组字段内。
{ $pushAll : { field : value_array } }
- $pull
- 从数组field内删除一个等于value值。
语法:db.collection.update( { field: <query> }, { $pull: { field: <query> } } );
{ $pull : { field : _value } }
//插入一条文档
db.profiles.insert({ votes: [ 3, 5, 6, 7, 7, 8 ] });
//移除数组中所有元素7
db.profiles.update( { votes: 3 }, { $pull: { votes: 7 } } );
//移除数组中所有大于6的元素
db.profiles.update( { votes: 3 }, { $pull: { votes: { $gt: 6 } } } );
//Result
{ votes: [ 3, 5, 6, 8 ] }
{ votes: [ 3, 5, 6 ] }
- $addToSet
- 增加一个值到数组内,而且只有当这个值不在数组内才增加。
- $pop
- 删除数组的第一个或最后一个元素
{ $pop : { field : 1 } }
- $rename
- 修改字段名称
$rename操作符可以重命名字段名称,新的字段名称不能和文档中现有的字段名相同。
如果文档中存在A、B字段,将B字段重命名为A,$rename会将A字段和值移除掉,然后将B字段名改为A.
当重命名子文档字段名时需要使用"."操作符,格式:值为该子文档的字段名.子文档中字段名。
$rename操作符也可以将子文档中键值移到其他子文档中。
db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } )
我们将名为name的子文档中的last字段,重名为“lname”,同时将其移动到子文档contact中,
若contact字段不存在,数据库会新建该字段。
{ $rename : { old_field_name : new_field_name } }
若指定的字段在集合中不存在,$rename操作符将不会有任何影响。
db.students.update( { _id: 1 }, { $rename: { 'wife': 'spouse' } } )
若指定的多个字段在集合中都不存在,$rename操作符将不会有任何影响。
db.students.update( { _id: 1 }, { $rename: { 'wife': 'spouse', 'vice': 'vp', 'office': 'term' } } )
集合中不存在上面语句中指定的wife、vice、office字段,所以上述的更新操作无任何影响。
若指定的多个字段中,有的在集合中存在,有的不存在,$rename操作符执行下列操作:
存在的字段按照上面的规则重命名为新的名称。
不存在的字段对数据无任何影响。
- $bit
- 位操作,integer类型
{$bit : { field : {and : 5}}}
- 偏移操作符
> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }
> t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )
> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
update() 方法
update() 方法用于更新已存在的文档。语法格式如下:
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
参数说明:
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别。
参考来源: http://www.runoob.com/mongodb/mongodb-atomic-operations.html