- MongoDB CRUD 操作 >
- MongoDB CRUD 教程 >
- 创建一个自增的序列字段
创建一个自增的序列字段¶
概要¶
MongoDB使用所有文档中的顶层文档里的 _id 字段作为主键。 _id 必须是唯一的,并且总是有使用 unique constraint 的索引。然而,除了唯一约束外,你可以在你的集合中为 _id 字段使用任何值。这个教程介绍了为 _id 字段创建自增序列数字的两种方法,如下:
注意事项¶
通常地,在MongoDB里,你不要为 _id 字段活或者任何字段使用自增长模型,因为它可扩展性不适合有大量文档的数据库(because it does not scale for databases with large numbers of documents)。通常默认值 ObjectId 对于 _id 来说是更加理想的。
(创建)过程¶
使用计数集合¶
计数集合实现¶
使用一个单独的 counters 集合来记录序列使用的 最新的 数字。 _id 字段包含序列名和包含序列最新值的 seq 字段。
把 userid 的初始值插入到 counters 集合中:
db.counters.insert( { _id: "userid", seq: 0 } )
创建一个接收序列 name 的 getNextSequence 函数。这个函数使用 findAndModify() 方法来原子地增加 seq 值并且返回这个新的值。
function getNextSequence(name) { var ret = db.counters.findAndModify( { query: { _id: name }, update: { $inc: { seq: 1 } }, new: true } ); return ret.seq; }
在 insert() 期间使用 getNextSequence() 函数。
db.users.insert( { _id: getNextSequence("userid"), name: "Sarah C." } ) db.users.insert( { _id: getNextSequence("userid"), name: "Bob D." } )
你可以使用 find() 方法验证结果:
db.users.find()
The _id fields contain incrementing sequence values:
{ _id : 1, name : "Sarah C." } { _id : 2, name : "Bob D." }
findAndModify 行为¶
当 findAndModify() 包含 upsert: true 选项 并且 查询字段不是唯一索引的时候,在某些情况下,这个方法可能会多次插入一个文档。例如,如果多个客户端每一个都使用相同的查询条件调用方法,并且这些方法在任一个方法执行修改阶段之前完成查找阶段,这些方法可能会插入相同的文档。
在 counters 集合例子中,查询字段是 _id 字段,它总是有一个唯一的索引。考虑 findAndModify() 包含 upsert: true 选项,如下面的修改所示:
function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true,
upsert: true
}
);
return ret.seq;
}
如果多个客户端调用拥有相同 name 参数的 getNextSequence() 方法,那么这个方法将遵守以下行为之一:
恰好一个 findAndModify() 方法成功插入一个新的文档。
零个或更多的 findAndModify() 方法更新那个新插入的文档。
零个或多个 findAndModify() 方法尝试插入一个重复数据的时候失败。
如果这个方法由于唯一索引约束冲突失败,那么重新执行这个方法。没有文档删除,重试应该不会失败。
乐观循环¶
在这个范例里面,一个 乐观循环 计算增长的 _id 值并且尝试插入一个包含计算出的 _id 值的文档。如果插入成功,那么循环将结束。否则,这个循环将迭代访问可能的 _id 值,知道插入成功为止。
创建一个命名为 insertDocument 的执行 “insert if not present” 循环的方法。这个方法包含 insert() 方法并且有 doc 和 targetCollection 参数。
在 2.6 版更改: The db.collection.insert() method now returns a 写入结果 object that contains the status of the operation. Previous versions required an extra db.getLastErrorObj() method call.
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; } }
The while (1) loop performs the following actions:
使用最大 _id 值为文档查询 targetCollection 。
通过下列方法来决定 _id 的下一个序列值:
如果返回的游标指向一个文档,则为返回的 _id 值加 1 。
否则:如果返回的游标不指向任何文档,则设置下一个序列值为 1 。
对于要插入的 doc ,设置它的 _id 字段为计算出的序列值 seq 。
把 doc 插入到 targetCollection 中。
如果插入操作错误为重复的键,继续这个循环。否则,如果插入操作遇到一些其他的错误或者这个操作成功,则跳出循环。
使用 insertDocument() 方法来执行插入:
var myCollection = db.users2; insertDocument( { name: "Grace H." }, myCollection ); insertDocument( { name: "Ted R." }, myCollection )
你可以使用 find() 方法验证结果:
db.users2.find()
The _id fields contain incrementing sequence values:
{ _id: 1, name: "Grace H." } { _id : 2, "name" : "Ted R." }
对于大量的插入, while 循环可能会执行许多次。