MongoDB如何实现插入后自动递增的ID来访问记录?
实现MongoDB中自动递增ID的正确方案
你之前考虑的查询当前最大id再递增的方案存在两个核心问题:一是并发场景下会出现重复ID(多个请求同时查询到相同的最大值,导致插入重复id);二是如果删除过文档,会复用已删除的id(如果这不符合你的业务需求)。
MongoDB官方推荐使用计数器集合的方式实现可靠的自动递增ID,通过原子操作避免并发问题,同时不受文档删除的影响。具体实现步骤如下:
1. 创建计数器集合的模型
首先定义一个专门存储各集合递增序列的计数器模型,用来记录每个集合当前的最大ID值:
const mongoose = require('mongoose'); const counterSchema = new mongoose.Schema({ collectionName: { type: String, required: true, unique: true // 确保每个集合对应唯一的计数器 }, sequenceValue: { type: Number, default: 0 // 初始值设为0,第一次插入会递增到1 } }); module.exports = mongoose.model('Counter', counterSchema);
2. 修改人员集合的Schema,添加自动递增逻辑
在人员的Schema中使用pre('save')中间件,在文档保存前通过原子操作获取下一个递增ID:
const mongoose = require('mongoose'); const Counter = require('./counter.model'); // 引入计数器模型 const personSchema = new mongoose.Schema({ id: { type: Number, unique: true // 唯一约束,作为重复ID的最后防线 }, name: String, role: String }); // 保存前自动生成递增ID personSchema.pre('save', async function(next) { const doc = this; // 仅在新增文档(未设置id)时生成ID if (!doc.id) { // 原子操作:找到对应集合的计数器并递增序列值 const counter = await Counter.findOneAndUpdate( { collectionName: 'Person' }, // 对应人员集合的名称 { $inc: { sequenceValue: 1 } }, // 原子递增操作 { new: true, upsert: true } // upsert:不存在则创建;new:返回更新后的计数器 ); doc.id = counter.sequenceValue; } next(); }); module.exports = mongoose.model('Person', personSchema);
方案优势
- 原子性保障:
findOneAndUpdate是MongoDB的原子操作,即使多个请求同时触发,也只会有一个请求能成功递增序列值,彻底避免并发重复ID问题。 - 不受文档删除影响:计数器独立存储,人员集合中的文档被删除后,计数器的序列值不会回退,因此不会出现复用已删除ID的情况(符合大多数业务对递增ID的要求)。
注意事项
- 确保计数器集合的
collectionName与实际集合名称一一对应,避免不同集合共用计数器。 - 批量插入文档时,
pre('save')会逐个处理文档,若想优化性能,可以一次性获取多个递增ID,减少数据库交互次数。
内容的提问来源于stack exchange,提问作者notaorb
相关产品推荐
相关产品推荐

