MongoDB数据库本身并没有提供创建自增字段的方法,不过在其官方文档中,有一篇介绍创建自增字段的一篇文章,虽然该文章是针对_id的,不过其实用在其他字段上道理都是相同的,可以通过如下两种方式来为文档创建自增字段:
- 使用计数集合(use counter collection)
这一种方法的思想就是额外创建一个用于跟踪自增序列中最后一个数的集合,比如叫做counters集合,在每次做插入操作之前,首先从这个counters中根据给定字段名称查询出包含有目前自增序列的尾部(即序列中的最后一个值)信息的文档,同时根据需求(比如每次自增的step,通常为每次加一)计算得到当前应该被插入到自增序列中的值,最后不要忘记更新counters集合中的文档。
function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true,
upsert: true
}
);
return ret.seq;
}
db.users.insert(
{
_id: getNextSequence("userid"),
name: "Sarah C."
}
)
上面的例子是官方文档中的例子没有改动,从这个方法来看,在面对并发的插入请求时,没有办法很好的解决重复插入的问题(各文档中自增字段出现重复的值),接下来介绍下一种方法。
- 乐观循环(optimistic loop)
首先在要为自增字段创建唯一索引,然后将插入操作放入一个无限循环中,每次插入时,在集合中查询出自增序列的尾部的值,根据需求(比如每次自增的step,通常为每次加一)为自增字段更新值,并插入数据库中,在插入后检查是否有错误发生,如果发生的错位代号为11000(键值重复,这也是为什么要为该字段创建唯一索引的原因),则循环执行上述操作,知道插入成功为止。首先先贴出官方的例子便于理解:
function insertDocument(doc, targetCollection) {
while (1) {
var cursor = targetCollection.find( {}, { _id: 1 } ).sort( { _id: -1 } ).limit(1);
var seq = cursor.hasNext() ? cursor.next()._id + 1 : 1;
doc._id = seq;
var results = targetCollection.insert(doc);
if( results.hasWriteError() ) {
if( results.writeError.code == 11000 /* dup key */ )
continue;
else
print( "unexpected error inserting data: " + tojson( results ) );
}
break;
}
}
相对于第一种方法而言,这个方法在面对并发问题时,可以较好的解决自增字段重复的问题。最近在用node.js做服务器端开发,需要写一个通用的RESTful Service,MongoDB作为后台数据库,在这个过程中有一个关于自增字段的需求,这里贴出我的node.js代码,其中用作MongoDB驱动的模块是Mongoose。
我在定义Mongoose需要的Schema中添加了有关自增字段的配置:
var schema = {
company: {
schema: {
companyId: Number,
clientId: Number,
name: String,
projects: [{
id: Number,
name: String
}]
},
option: {
collection: "companies",
autoIncrementField: "companyId",
autoIncrementStartValue: 1000,
autoIncrementStep: 1
}
},
...
}
由于mongoose中查询数据库都是通过异步回调的方式,不能直接使用while(1)...break的方式,于是我使用了递归函数来实现相同的效果:
function handleAutoInc(model, autoIncField, startValue, step, argObj, callback) {
var projection = {};
projection[autoIncField] = 1;
var queryObj = {};
queryObj['$query'] = {};
queryObj['$orderby'] = {};
queryObj['$orderby'][autoIncField] = -1;
model.find(queryObj, projection, function(err, results){
if(err) {
callback(err);
} else {
var last = results[0];
var seq = last ? last[autoIncField] + step : startValue ? startValue : 1;
argObj[autoIncField] = seq;
var newModel = new model(argObj);
newModel.save(function(err){
if(err) {
if(err.code == 11000) {
handleAutoInc(model, autoIncField, startValue, step, argObj, callback);
} else {
callback(err);
}
} else {
callback();
}
});
}
});
}
其他的具体实现的代码在这里就不一一贴出了,有兴趣的朋友可以和我一起探讨,另外,若对本篇文章中所述有任何异议,欢迎及时提出,谢谢。