MongoDB 自动增长(Auto-Increment)完整指南
MongoDB 是 文档型数据库,没有原生的 AUTO_INCREMENT 字段(不像 MySQL)。但可以通过多种方式实现 自增 ID,适用于需要 顺序编号、订单号、票据号、用户 ID 等场景。
1. 为什么需要自增 ID?
| 需求 | 说明 |
|---|---|
| 业务顺序 | 订单号 ORD000001、ORD000002 |
| 可读性 | 人类可识别的连续编号 |
| 报表统计 | 按编号范围查询 |
| 兼容旧系统 | 迁移自 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()
})
}
参考资料
- 官方文档:findOneAndUpdate
- MongoDB 6.0 新特性:Auto Increment
- 最佳实践:MongoDB Schema Design
一句话总结
MongoDB 无原生自增,但用
counters+findOneAndUpdate(经典)或 MongoDB 6.0+autoIncrement(推荐)可实现高性能、并发安全的自动增长 ID。
需要:
- Python / Java / Go 示例代码?
- 带前缀 + 年月的发票号生成器?
- 迁移 MySQL
AUTO_INCREMENT到 MongoDB 的脚本?
欢迎继续提问!