资讯专栏INFORMATION COLUMN

利用mongo的findAndModify原子性操作实现auto increment ID

genedna / 1542人阅读

摘要:实际情况默认情况下,使用自动生成,而且在自带的命令里,无法指定一个自增字段。自增字段在多线程时必须是原子性的,这在大数据情况下很难实现伸缩性。而且,在里面,有一个命令是原子性的。代码如下无法打开创建个模拟多线程环境输出查询结果

实际情况

默认情况下,mongo使用_id自动生成uniq id,而且在mongo自带的命令里,无法指定一个自增字段。自增字段在多线程时必须是原子性的,这在大数据情况下很难实现伸缩性(scalability)。

Generally in MongoDB, you would not use an auto-increment pattern for
the _id field, or any field, because it does not scale for databases
with large numbers of documents. Typically the default value ObjectId
is more ideal for the _id.

不过好消息是,_id不一定非得是ObjectId类型,它可以是任何类型。而且,在mongo里面,有一个findAndModify命令是原子性的。我们可以使用它实现auto increment ID。
在golang的mgo库里,Apply命令可以实现findAndModify功能

具体策略和代码 策略

假设我们要把test表的_id字段设置为自增的,我们需要先创建一个counter表,这个表里面保存一个seq字段,通过在seq上使用Apply,获取新doc的seq值作为test表中下一个id的值。

代码如下:
package main

import (
    "fmt"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "log"
    "math/rand"
    "sync"
)

func init() {
    log.SetFlags(log.Lshortfile | log.LstdFlags)
}
func main() {
    session, err := mgo.Dial("usr:pwd@127.0.0.1:27017/dbname")
    if err != nil {
        log.Fatal("无法打开MongoDB!")
        return
    }
    defer session.Close()

    clt := session.DB("dbname").C("test")
    getNextSeq := func() func() int {
        counter := session.DB("dbname").C("counter")
        cid := "counterid"
        return func() int {
            change := mgo.Change{
                Update:    bson.M{"$inc": bson.M{"seq": 1}},
                Upsert:    true,
                ReturnNew: true,
            }
            doc := struct{ Seq int }{}
            if _, err := counter.Find(bson.M{"_id": cid}).Apply(change, &doc); err != nil {
                panic(fmt.Errorf("get counter failed:", err.Error()))
            }
            log.Println("seq:", doc)
            return doc.Seq
        }
    }()

    wg := sync.WaitGroup{}
    // 创建10个 go routine模拟多线程环境
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            _row := map[string]interface{}{
                "_id":       getNextSeq(),
                "username":  fmt.Sprintf("name%2d", i),
                "telephone": fmt.Sprintf("tel%4d", rand.Int63n(1000))}
            if err := clt.Insert(_row); err != nil {
                log.Printf("insert %v failed: %v
", _row, err)
            } else {
                log.Printf("insert: %v success!
", _row)
            }
            wg.Done()
        }(i)
    }
    wg.Wait()
}
输出:
2016/04/28 17:05:28 main.go:37: seq: {201}
2016/04/28 17:05:28 main.go:54: insert: map[_id:201 username:name 9 telephone:tel 410] success!
2016/04/28 17:05:28 main.go:37: seq: {204}
2016/04/28 17:05:28 main.go:37: seq: {203}
2016/04/28 17:05:28 main.go:37: seq: {202}
2016/04/28 17:05:28 main.go:37: seq: {206}
2016/04/28 17:05:28 main.go:37: seq: {205}
2016/04/28 17:05:28 main.go:54: insert: map[_id:202 username:name 5 telephone:tel  51] success!
2016/04/28 17:05:28 main.go:54: insert: map[_id:204 username:name 2 telephone:tel 551] success!
2016/04/28 17:05:28 main.go:54: insert: map[telephone:tel 821 _id:203 username:name 1] success!
2016/04/28 17:05:28 main.go:37: seq: {207}
2016/04/28 17:05:28 main.go:54: insert: map[_id:205 username:name 6 telephone:tel 320] success!
2016/04/28 17:05:28 main.go:54: insert: map[_id:206 username:name 4 telephone:tel 937] success!
2016/04/28 17:05:28 main.go:54: insert: map[_id:207 username:name 3 telephone:tel 758] success!
2016/04/28 17:05:28 main.go:37: seq: {208}
2016/04/28 17:05:28 main.go:54: insert: map[username:name 7 telephone:tel 148 _id:208] success!
2016/04/28 17:05:28 main.go:37: seq: {209}
2016/04/28 17:05:28 main.go:54: insert: map[_id:209 username:name 0 telephone:tel 216] success!
2016/04/28 17:05:28 main.go:37: seq: {210}
2016/04/28 17:05:28 main.go:54: insert: map[_id:210 username:name 8 telephone:tel 449] success!
mongo查询结果
db.test.find({_id:{$gt:200}})
{ "_id" : 201, "username" : "name 9", "telephone" : "tel 410" }
{ "_id" : 202, "username" : "name 5", "telephone" : "tel  51" }
{ "_id" : 203, "username" : "name 1", "telephone" : "tel 821" }
{ "_id" : 204, "username" : "name 2", "telephone" : "tel 551" }
{ "_id" : 205, "username" : "name 6", "telephone" : "tel 320" }
{ "_id" : 206, "username" : "name 4", "telephone" : "tel 937" }
{ "_id" : 207, "username" : "name 3", "telephone" : "tel 758" }
{ "_id" : 208, "username" : "name 7", "telephone" : "tel 148" }
{ "_id" : 209, "username" : "name 0", "telephone" : "tel 216" }
{ "_id" : 210, "username" : "name 8", "telephone" : "tel 449" }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/18838.html

相关文章

  • MongoDB(三):创建、更新和删除文档

    摘要:排序结果的条件修改器条件,用户对匹配的文档进行更新和必须指定一个布尔类型,表示是否删除文档和必须指定一个布尔类型,表示返回更新前的文档还是更新后的文档,默认是更新前的文档。 本文所有内容以MongoDB 3.2 为基础。 插入并保存文档 插入是添加数据的基本方法。可以使用insert插入一个文档: db.foo.insert({bar: baz}) 批量插入 使用批量插入,可以加快插入...

    zorro 评论0 收藏0
  • 有坑勿踩(三)——关于数据更新

    摘要:前言数据更新,中的,对任何数据库而言都是最基本的操作。你并不能保证数据在被你读出来到写回去期间是否有别人已经改了数据库中的记录,这就是第一个风险,操作存在潜在的可能性会覆盖掉别人更新过的数据。 前言 数据更新,CRUD中的U,对任何数据库而言都是最基本的操作。看似简单的更新操作中会藏着哪些坑?今天聊一聊这个话题。 在写这个系列文章时,我会假设读者已经对MongoDB有了最基础的了解,因...

    mengera88 评论0 收藏0
  • MongoDB指南---6、更新文档

    摘要:所以,两个需要同时进行的更新会迅速接连完成,此过程不会破坏文档最新的更新会取得胜利。可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。 上一篇文章:MongoDB指南---5、创建、删除文档下一篇文章:MongoDB指南---7、find简介与查询条件 文档存入数据库以后,就可以使用update方法来更新它。update有两个参数,一个是查询文档,用于定位需要更新的目标文档...

    zero 评论0 收藏0
  • MongoDB指南---6、更新文档

    摘要:所以,两个需要同时进行的更新会迅速接连完成,此过程不会破坏文档最新的更新会取得胜利。可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。 上一篇文章:MongoDB指南---5、创建、删除文档下一篇文章:MongoDB指南---7、find简介与查询条件 文档存入数据库以后,就可以使用update方法来更新它。update有两个参数,一个是查询文档,用于定位需要更新的目标文档...

    lscho 评论0 收藏0
  • Mongo语法总结

    摘要:先进行过滤,再分组实例解释进行过滤,这里利用两个字段进行过滤。聚合操作可以对分组的数据执行如下的表达式计算计算总和。根据分组,获取集合中所有文档对应值得最大值。将指定的表达式的值添加到一个数组中。 先进行过滤,再分组 1、实例: db.getCollection(UpMsgItem).aggregate( [ {$match : { createTime : {$gt : ...

    shmily 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<