MongoDB 自动增长

MongoDB 自动增长(Auto-Increment)完整指南

MongoDB 是 文档型数据库没有原生的 AUTO_INCREMENT 字段(不像 MySQL)。但可以通过多种方式实现 自增 ID,适用于需要 顺序编号、订单号、票据号、用户 ID 等场景。


1. 为什么需要自增 ID?

需求说明
业务顺序订单号 ORD000001ORD000002
可读性人类可识别的连续编号
报表统计按编号范围查询
兼容旧系统迁移自 MySQL 等关系型数据库

2. 推荐方案对比

方案并发安全性能复杂度推荐场景
Counters 集合高(findAndModify通用
MongoDB 6.0+ autoIncrement极低新项目
UUID + 序列号拼接分布式
应用层生成低(需锁)不推荐

方案一:Counters 集合(经典通用)

原理

使用一个独立的 counters 集合存储每个序列的当前值,通过原子操作 findAndModify(现为 findOneAndUpdate)获取并递增。

步骤

// 1. 初始化计数器(只需执行一次)
db.counters.insertOne({
  _id: "order_id",
  seq: 0
})

获取下一个 ID

function getNextSequence(name) {
  const ret = db.counters.findOneAndUpdate(
    { _id: name },
    { $inc: { seq: 1 } },
    { returnNewDocument: true, upsert: true }
  )
  return ret.seq
}

// 使用
const nextId = getNextSequence("order_id")
db.orders.insertOne({ _id: nextId, order_no: `ORD${String(nextId).padStart(6,'0')}`, ... })

Node.js 示例

async function getNextId(collName) {
  const db = client.db('mydb')
  const counter = await db.collection('counters').findOneAndUpdate(
    { _id: collName },
    { $inc: { seq: 1 } },
    { returnDocument: 'after', upsert: true }
  )
  return counter.seq
}

// 使用
const id = await getNextId('user_id')
await db.collection('users').insertOne({ _id: id, name: 'Alice' })

优点:并发安全、简单可靠
缺点:每插入一次需额外查询


方案二:MongoDB 6.0+ 原生 autoIncrement(推荐)

MongoDB 6.0 引入 $inc + default 序列,可实现 字段级自增

创建带自增字段的集合

db.createCollection("products", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      properties: {
        product_id: { bsonType: "int" }
      }
    }
  }
})

// 设置自增规则(只需一次)
db.runCommand({
  collMod: "products",
  autoIncrement: {
    field: "product_id",
    start: 1000,
    step: 1
  }
})

插入时自动生成

db.products.insertOne({ name: "iPhone" })
// → { _id: ObjectId(...), product_id: 1000, name: "iPhone" }

db.products.insertOne({ name: "MacBook" })
// → product_id: 1001

查询当前序列值

db.products.aggregate([{ $group: { _id: null, maxId: { $max: "$product_id" } } }])

优点:无需额外集合、性能高、语法简洁
要求:MongoDB ≥ 6.0


方案三:前缀 + 填充格式化(业务编号)

function formatOrderNo(seq) {
  return `ORD${new Date().getFullYear()}${String(seq).padStart(6, '0')}`
}

// 示例输出:ORD2025000123
const seq = await getNextId('order_seq')
const orderNo = formatOrderNo(seq)
db.orders.insertOne({ order_no: orderNo, amount: 99.9 })

方案四:分布式自增(多实例)

使用 counters + 分片键

// 按年分片
db.counters.insertOne({ _id: { year: 2025, type: "invoice" }, seq: 0 })

// 获取
db.counters.findOneAndUpdate(
  { _id: { year: 2025, type: "invoice" } },
  { $inc: { seq: 1 } },
  { upsert: true, returnDocument: 'after' }
)

方案五:应用层生成(不推荐)

let counter = 0
setInterval(() => counter++, 100)  // 错误!并发冲突

风险:多实例竞争、崩溃丢失、重启重复


性能对比(每秒插入)

方案QPS(单节点)并发安全
Counters + findOneAndUpdate~8,000
MongoDB 6.0+ autoIncrement~12,000
应用层< 1,000

最佳实践

场景推荐方案
新项目(MongoDB 6.0+)原生 autoIncrement
订单/发票编号counters + 格式化前缀
用户 ID(可间断)使用 ObjectId 或 UUID
高并发写入counters + 批量获取

批量获取 ID(提升性能)

async function getIdBatch(name, count) {
  const ret = await db.counters.findOneAndUpdate(
    { _id: name },
    { $inc: { seq: count } },
    { returnDocument: 'after', upsert: true }
  )
  const start = ret.seq - count + 1
  return Array.from({ length: count }, (_, i) => start + i)
}

// 一次获取 100 个 ID
const ids = await getIdBatch('ticket_id', 100)

常见问题(FAQ)

问题解答
自增 ID 会重复吗?不会,findOneAndUpdate 是原子操作
可以重置序列吗?可以:db.counters.updateOne({_id: "x"}, {$set: {seq: 0}})
支持分片吗?是,counters 可分片,建议以 _id 为 shard key
ObjectId 能不能代替?不行,ObjectId 是 12 字节十六进制,非递增
可以跳号吗?可以,失败事务、回滚、批量预取都会造成跳号(业务可接受)

完整示例:订单系统

// 初始化
db.counters.insertOne({_id: "order_id", seq: 1000})

// 获取订单号
async function createOrder(data) {
  const counter = await db.counters.findOneAndUpdate(
    {_id: "order_id"},
    {$inc: {seq: 1}},
    {returnDocument: 'after'}
  )
  const orderNo = `ORD${String(counter.seq).padStart(8, '0')}`

  return await db.orders.insertOne({
    order_no: orderNo,
    ...data,
    created_at: new Date()
  })
}

参考资料


一句话总结

MongoDB 无原生自增,但用 counters + findOneAndUpdate(经典)或 MongoDB 6.0+ autoIncrement(推荐)可实现高性能、并发安全的自动增长 ID。


需要

  • Python / Java / Go 示例代码?
  • 带前缀 + 年月的发票号生成器?
  • 迁移 MySQL AUTO_INCREMENT 到 MongoDB 的脚本?

欢迎继续提问!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部