前言
熟悉
MongoDB
的用户应该都知道,它并不像一些关系型数据库那样提供内置的自增
ID
功能,而是默认使用
ObjectId
作为主键的类型。但有时使用自增
ID
可能更符合某些应用场景的需求,例如:
-
兼容现有系统
某些系统需要将数据迁移到
MongoDB时,如果原来使用的是自增ID作为主键,在迁移过去之后需要保持自增主键的特点。 -
对外展示的
ID在一些应用场景中,一个更直观、更易记的标识符,对用户更友好,例如展示给用户的 用户编号 、 文章编号 等。这在需要手动输入或与用户交流时特别有用,因为自增ID比ObjectId更短、更易读。
虽然
MongoDB
不支持自增
ID
的功能,但我们仍然可以使用其他方式来实现此功能。本文将会介绍如何在
MongoDB
中实现自增
ID
序号。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
基于计数器集合实现自增序号
创建自增序号的集合
我们可以使用计数器集合
counters
来实现实现自增序号,这也是官方推荐的一种实现方式。
counters
集合的文档结构如下:
{
"_id": "posts",
"seq_value": 1
该集合有两个字段:
-
_id:代表某个集合的名称。 -
seq_value:为自增序号。
由于
counters
集合中的
_id
字段值代表某个集合的名称,因此我们可以利用
counters
集合为多个集合实现自增
序号
,而不仅限于单个集合。
实现自增序号的方法
那么
counters
集合要怎么实现
seq_value
字段的自增呢?这就需要用到
findOneAndUpdate
方法了。
findOneAndUpdate
方法用于查找并更新集合中的单个文档。该方法还支持选择性地返回更新前或更新后的文档。
下面是一个简单案例的具体流程:
- 开始:流程图从“开始”节点开始。
-
创建
posts文章和counters计数器集合。db.createCollection("posts"); db.createCollection("counters"); -
获取自增
序号
:使用
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; -
向
posts集合中插入新文档:使用从counters集合中获取的自增seq_value作为新文档的一个字段,插入到posts集合中。db.posts.insertOne({ title: "在 MongoDB 中实现自增 ID", author: "陈明勇", seq_value: seqValue - 结束:流程结束。
完整的脚本示例代码
下面是完整的
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) // 返回自增序号