Prisma中更新字段时如何安全生成唯一自增整数值
方案1:使用数据库原生序列(推荐,从根源避免竞态)
数据库提供的序列对象天生是原子操作,多并发场景下也不会生成重复值,是最稳妥的实现方式。
以PostgreSQL为例:
- 先在数据库中创建全局序列:
CREATE SEQUENCE published_document_number_seq START 1;
- 发布文档时直接调用序列的
nextval方法取值即可,Prisma中可以用原生查询配合更新:
const updatedDoc = await prisma.$executeRaw` UPDATE "Document" SET draft = false, "publishedDocumentNumber" = nextval('published_document_number_seq') WHERE id = ${req.body.id} AND draft = true; `;
如果用MySQL,也可以创建专门的计数表模拟序列,用UPDATE ... RETURNING的原子操作取最新值,同样不会有并发问题。
方案2:Prisma事务加锁实现
如果不想额外维护序列,可以用可重复读隔离级别的事务配合行锁,保证同一时间只有一个事务能计算最新编号:
const updatedDoc = await prisma.$transaction(async (tx) => { // 先锁所有已发布的文档行,避免其他事务同时读取最大值,锁语句根据数据库类型调整 const maxNumberResult = await tx.document.aggregate({ _max: { publishedDocumentNumber: true }, lock: { mode: 'write' } }); // 兼容首次发布没有值的情况 const nextNumber = (maxNumberResult._max.publishedDocumentNumber ?? 0) + 1; // 执行更新,额外加draft=true的条件避免重复发布 return tx.document.update({ where: { id: req.body.id, draft: true }, data: { draft: false, publishedDocumentNumber: nextNumber } }); }, { isolationLevel: 'RepeatableRead' });
方案3:唯一索引兜底重试
因为你已经给publishedDocumentNumber加了@unique约束,就算出现并发冲突,数据库会直接抛出唯一约束错误,你可以捕获这个错误重试2-3次即可,作为兜底机制:
async function publishDocument(docId: string, retryCount = 0) { try { return await prisma.$transaction(async (tx) => { const maxNum = await tx.document.aggregate({ _max: { publishedDocumentNumber: true } }); const nextNum = (maxNum._max.publishedDocumentNumber ?? 0) + 1; return tx.document.update({ where: { id: docId, draft: true }, data: { draft: false, publishedDocumentNumber: nextNum } }); }); } catch (e) { // 捕获唯一约束冲突错误,重试最多3次 if (e.code === 'P2002' && retryCount < 3) { return publishDocument(docId, retryCount + 1); } throw e; } }
以上三种方案都可以完全避免count方法带来的竞态问题,优先推荐方案1,维护成本最低,可靠性最高。
内容的提问来源于stack exchange,提问作者circadian
相关产品推荐
相关产品推荐

