使用Drizzle ORM+PostgreSQL插入自增sku及事务异常解决方案咨询
问题原因与解决方案
真实原因
PostgreSQL 默认采用 READ COMMITTED 事务隔离级别,在这个级别下,事务内的查询只能看到事务启动前已经提交的数据,无法读取当前事务中尚未提交的插入/修改结果。
你在事务中先执行插入操作,紧接着用 SELECT MAX(sku) 计算新sku值时,这个子查询看不到刚插入的那条数据:
- 如果是第一条数据,会错误地将sku设为100000(即便插入操作已经执行)
- 如果已有其他数据,会基于事务启动前的最大值加1,甚至在并发场景下导致sku重复
最优解决方案
方案1:使用PostgreSQL序列(推荐)
数据库序列是原子性的自增生成器,既能解决事务内的可见性问题,又能彻底避免并发场景下的sku重复,是最可靠的方案。
步骤:
- 创建从100000开始的序列:
CREATE SEQUENCE article_sku_seq START 100000;
- 修改
articles表的sku字段,设置默认值为序列的下一个值:
ALTER TABLE articles ALTER COLUMN sku SET DEFAULT nextval('article_sku_seq');
- 插入数据时无需手动处理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
相关产品推荐
相关产品推荐

