Prisma使用MongoDB数据源时创建自增ID的正确方法
Prisma + MongoDB 自增序列实现方案
问题说明
Prisma 对接 MongoDB 数据源时,模型字段配置的autoincrement()默认值函数无法生效,示例模型定义如下:
model User { id String @id @default(auto()) @map("_id") @db.ObjectId sequence Int @unique @default(autoincrement()) // 该配置在MongoDB下无法生效 email String @unique password String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
常规「查询表内最后一条记录取最大序列值+1」的实现存在全表扫描开销,且并发场景下容易出现序列重复,需要更低开销、可在创建记录流程内直接完成序列赋值的实现方式,效果对齐 Mongoose 的自增能力。
实现原理
Prisma 提供的autoincrement()是基于关系型数据库原生的自增列特性封装的,MongoDB 本身没有表级自增字段的原生支持,Mongoose 生态的自增能力本质也是依赖独立的计数器集合做原子自增,该方案在 Prisma 中可直接复用,无额外性能损耗,且能保证并发安全。
具体实现步骤
- 新增计数器模型
在schema.prisma中新增独立的Counter模型,用于存储各业务模型的当前自增序列值:
model Counter { id String @id @default(auto()) @map("_id") @db.ObjectId model String @unique // 绑定需要自增序列的模型名,例如此处存储"User" seq Int @default(0) // 存储对应模型的最新序列值 }
模型定义完成后执行prisma generate、prisma db push将结构同步到 MongoDB 实例。
初始化计数器记录
首次使用前,为User模型插入一条初始计数器数据,seq字段初始值设为0即可。封装原子创建逻辑
基于Prisma交互式事务+MongoDB原子更新能力,在创建用户的同一事务内完成序列自增、记录创建操作,全程无全表扫描开销:
async function createUser(userPayload: { email: string; password: string }) { return prisma.$transaction(async (tx) => { // 原子更新计数器,序列值+1后直接返回最新值 const updatedCounter = await tx.counter.update({ where: { model: "User" }, data: { seq: { increment: 1 } }, }) // 使用返回的最新序列值创建用户记录 const newUser = await tx.user.create({ data: { ...userPayload, sequence: updatedCounter.seq, }, }) return newUser }) }
方案优势
- 性能开销极低:计数器集合的查询、更新都走唯一索引,不存在全表扫描,和关系型数据库原生自增的性能差距可以忽略
- 并发安全:序列自增是MongoDB层面的原子操作,配合事务保证序列更新和记录创建的原子性,不会出现并发场景下的序列重复、跳号问题
- 扩展性强:后续需要给其他模型加自增序列时,只需要在Counter集合中插入对应模型的初始记录,复用同一套逻辑即可
注意:不要使用「查询User表最大sequence值+1」的方案,除了全表扫描的性能问题外,并发场景下多个请求会读取到相同的最大值,必须加分布式锁才能避免序列重复,整体性能和可靠性都远低于独立计数器方案。
内容的提问来源于stack exchange,提问作者Danilo Cunha
相关产品推荐
相关产品推荐

