You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Drizzle ORM+PostgreSQL插入自增sku及事务异常解决方案咨询

问题原因与解决方案

真实原因

PostgreSQL 默认采用 READ COMMITTED 事务隔离级别,在这个级别下,事务内的查询只能看到事务启动前已经提交的数据,无法读取当前事务中尚未提交的插入/修改结果。

你在事务中先执行插入操作,紧接着用 SELECT MAX(sku) 计算新sku值时,这个子查询看不到刚插入的那条数据:

  • 如果是第一条数据,会错误地将sku设为100000(即便插入操作已经执行)
  • 如果已有其他数据,会基于事务启动前的最大值加1,甚至在并发场景下导致sku重复

最优解决方案

方案1:使用PostgreSQL序列(推荐)

数据库序列是原子性的自增生成器,既能解决事务内的可见性问题,又能彻底避免并发场景下的sku重复,是最可靠的方案。

步骤:

  1. 创建从100000开始的序列:
CREATE SEQUENCE article_sku_seq START 100000;
  1. 修改articles表的sku字段,设置默认值为序列的下一个值:
ALTER TABLE articles ALTER COLUMN sku SET DEFAULT nextval('article_sku_seq');
  1. 插入数据时无需手动处理sku,数据库自动赋值:
const createdArticle = await db.transaction(async (tx) => {
  const [article] = await tx.insert(articles).values(newArticle).returning();
  return article;
});

如果用Drizzle Schema定义,可以直接集成序列:

import { pgTable, serial, pgSequence } from 'drizzle-orm/pg-core';

// 定义序列
const articleSkuSeq = pgSequence('article_sku_seq').start(100000);

export const articles = pgTable('articles', {
  id: serial('id').primaryKey(),
  // 其他字段...
  sku: serial('sku').default(sql`nextval('article_sku_seq')`),
});

执行Drizzle迁移时,会自动创建序列并设置字段默认值。

方案2:事务内预计算sku(仅临时场景)

如果暂时无法修改数据库结构,可以在事务内先计算sku值,再执行插入操作:

const createdArticle = await db.transaction(async (tx) => {
  // 先获取当前最大sku,基于事务启动前的提交数据计算
  const [maxSkuRow] = await tx.select({ maxSku: sql`COALESCE(MAX(sku), 99999)` }).from(articles);
  const newSku = maxSkuRow.maxSku + 1;
  
  // 插入时直接设置sku
  const [article] = await tx.insert(articles).values({ ...newArticle, sku: newSku }).returning();
  return article;
});

⚠️ 注意:该方案在高并发场景下会出现sku重复问题,仅适合低并发或测试环境。

方案3:调整事务隔离级别(不推荐)

将事务隔离级别改为 REPEATABLE READ 或 SERIALIZABLE,可以让事务内的查询看到自身未提交的修改,但会降低数据库并发性能,且SERIALIZABLE级别可能导致事务重试,不建议在生产环境使用。

内容的提问来源于stack exchange,提问作者hantoren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 10:25:32