陈明勇 一名热爱技术、乐于分享的开发者,同时也是开源爱好者。
94 文章
12 分类
22 标签
20 评论
173 点赞
106860 浏览量

扫码关注公众号,手机阅读更方便

Go技术干货

前言

熟悉 MongoDB 的用户应该都知道,它并不像一些关系型数据库那样提供内置的自增 ID 功能,而是默认使用 ObjectId 作为主键的类型。但有时使用自增 ID 可能更符合某些应用场景的需求,例如:

  • 兼容现有系统 某些系统需要将数据迁移到 MongoDB 时,如果原来使用的是自增 ID 作为主键,在迁移过去之后需要保持自增主键的特点。
  • 对外展示的 ID 在一些应用场景中,一个更直观、更易记的标识符,对用户更友好,例如展示给用户的 用户编号 文章编号 等。这在需要手动输入或与用户交流时特别有用,因为自增 ID ObjectId 更短、更易读。

虽然 MongoDB 不支持自增 ID 的功能,但我们仍然可以使用其他方式来实现此功能。本文将会介绍如何在 MongoDB 中实现自增 ID 序号。

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

let's go

基于计数器集合实现自增序号

创建自增序号的集合

我们可以使用计数器集合 counters 来实现实现自增序号,这也是官方推荐的一种实现方式。 counters 集合的文档结构如下:

{
    "_id": "posts",
    "seq_value": 1

该集合有两个字段:

  • _id :代表某个集合的名称。
  • seq_value :为自增序号。

由于 counters 集合中的 _id 字段值代表某个集合的名称,因此我们可以利用 counters 集合为多个集合实现自增 序号 ,而不仅限于单个集合。

实现自增序号的方法

那么 counters 集合要怎么实现 seq_value 字段的自增呢?这就需要用到 findOneAndUpdate 方法了。

findOneAndUpdate 方法用于查找并更新集合中的单个文档。该方法还支持选择性地返回更新前或更新后的文档。

下面是一个简单案例的具体流程:

请在此添加图片描述

  1. 开始:流程图从“开始”节点开始。
  2. 创建 posts 文章和 counters 计数器集合。
    db.createCollection("posts");
    db.createCollection("counters");
    
  3. 获取自增 序号 :使用 findOneAndUpdate counters 集合中获取并自增 seq_value 。如果 counters 集合中 _id posts 的文档不存在,则通过 upsert: true 选项自动创建该文档,并初始化 seq_value 1
    const seqValue = db.counters.findOneAndUpdate(
      { _id: 'posts' },
      { $inc: { seq_value: 1 }},
      { returnDocument: "after", upsert: true }
    ).seq_value;
    
  4. posts 集合中插入新文档:使用从 counters 集合中获取的自增 seq_value 作为新文档的一个字段,插入到 posts 集合中。
    db.posts.insertOne({ 
      title: "在 MongoDB 中实现自增 ID",
      author: "陈明勇",
      seq_value: seqValue
    
  5. 结束:流程结束。

完整的脚本示例代码

下面是完整的 MongoDB 脚本示例代码,展示了如何创建集合、获取自增序号并插入新文档。

// 创建 posts 和 counters 集合
db.createCollection("posts");
db.createCollection("counters");
// 获取自增的 seq_value
const seqValue = db.counters.findOneAndUpdate(
  { _id: 'posts' },
  { $inc: { seq_value: 1 }},
  { returnDocument: "after", upsert: true }
).seq_value;
// 向 posts 集合中插入新文档
db.posts.insertOne({ 
  title: "在 MongoDB 中实现自增 ID",
  author: "陈明勇",
  seq_value: seqValue

Go 语言代码示例

  • Go 项目里安装 go mongox 模块
    go get github.com/chenmingyong0423/go-mongox
    
  • 完整代码
    package main
    import (
        "context"
        "fmt"
        "github.com/chenmingyong0423/go-mongox"
        "github.com/chenmingyong0423/go-mongox/builder/query"
        "github.com/chenmingyong0423/go-mongox/builder/update"
        "go.mongodb.org/mongo-driver/mongo"
        "go.mongodb.org/mongo-driver/mongo/options"
        "go.mongodb.org/mongo-driver/mongo/readpref"
    type Post struct {
        Title    string `bson:"title"`
        Author   string `bson:"author"`
        SeqValue int64  `bson:"seq_value"`
    type Counter struct {
        Id       string `bson:"_id"`
        SeqValue int64  `bson:"seq_value"`
    var db *mongo.Database
    func init() {
        client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{
            Username:   "test",
            Password:   "test",
            AuthSource: "db-test",
        if err != nil {
            panic(err)
        err = client.Ping(context.Background(), readpref.Primary())
        if err != nil {
            panic(err)
        db = client.Database("db-test")
    func main() {
        postColl := mongox.NewCollection[Post](db.Collection("posts"))
        seqValue, err := getNextSeqValue("posts")
        if err != nil {
            panic(err)
        fmt.Println(seqValue) // 如果是第一次执行 FindOneAndUpdate,值为 1
        // 插入一个 Post 文档,seq_value 字段为 Counter 文档的 seq_value 字段值
        insertOneResult, err := postColl.Creator().InsertOne(context.Background(), &Post{
            Title:    "在 MongoDB 中实现自增 ID",
            Author:   "陈明勇",
            SeqValue: seqValue,
        if err != nil {
            panic(err)
        // 验证插入的 Post 文档的 seq_value 字段值是否为 Counter 文档的 seq_value 字段值
        post, err := postColl.Finder().Filter(query.Id(insertOneResult.InsertedID)).FindOne(context.Background())
        if err != nil {
            panic(err)
        fmt.Println(post.SeqValue == seqValue) // true
    func getNextSeqValue(collectionName string) (int64, error) {
        // 创建 Counter 泛型集合
        counterColl := mongox.NewCollection[Counter](db.Collection("counters"))
        // 执行 FindOneAndUpdate 操作,如果不存在,则插入一个新的 Counter 文档,否则更新 seq_value 字段自增 1,并返回新增或更新后的 Counter 文档
        counter, err := counterColl.Finder().Filter(query.Id(collectionName)).Updates(update.Inc("seq_value", 1)).FindOneAndUpdate(context.Background(), options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After))
        if err != nil {
            panic(err)
        // 返回自增序号