OPTIONS
翻译或纠错本页面

创建一个自增的序列字段

概要

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 字段。

  1. userid 的初始值插入到 counters 集合中:

    db.counters.insert(
       {
          _id: "userid",
          seq: 0
       }
    )
    
  2. 创建一个接收序列 namegetNextSequence 函数。这个函数使用 findAndModify() 方法来原子地增加 seq 值并且返回这个新的值。

    function getNextSequence(name) {
       var ret = db.counters.findAndModify(
              {
                query: { _id: name },
                update: { $inc: { seq: 1 } },
                new: true
              }
       );
    
       return ret.seq;
    }
    
  3. 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 值,知道插入成功为止。

  1. 创建一个命名为 insertDocument 的执行 “insert if not present” 循环的方法。这个方法包含 insert() 方法并且有 doctargetCollection 参数。

    在 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 中。

    • 如果插入操作错误为重复的键,继续这个循环。否则,如果插入操作遇到一些其他的错误或者这个操作成功,则跳出循环。

  2. 使用 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 循环可能会执行许多次。