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

PostgreSQL触发器生成唯一订单号并发冲突问题求解

数据库层面最优解决方法

问题根源

原触发器通过COUNT(*)获取对应国家的订单数量来生成流水号,并发场景下多个事务会同时读到相同的COUNT值,导致生成重复的订单号,最终触发唯一约束报错。

方案一:专用流水号表+行级锁(推荐)

创建单独的表存储每个国家的当前最大流水号,生成订单号时通过行级锁锁定对应国家的记录,确保同一时间只有一个事务能获取并更新流水号,从根源避免并发冲突。

1. 创建流水号维护表

CREATE TABLE country_order_seq (
    country_code VARCHAR(10) PRIMARY KEY, -- 与order_main的order_country_code对应
    current_seq INT NOT NULL DEFAULT 0    -- 当前最大流水号
);

2. 初始化已有国家的流水号

如果已有订单数据,先同步初始化该表:

INSERT INTO country_order_seq (country_code, current_seq)
SELECT order_country_code, COUNT(*)
FROM order_main
GROUP BY order_country_code;

3. 修改触发器函数

CREATE OR REPLACE FUNCTION func_gen_order_no()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
DECLARE
    next_seq INT;
BEGIN
    -- 锁定对应国家的行,防止并发读取重复值
    SELECT current_seq + 1 INTO next_seq
    FROM country_order_seq
    WHERE country_code = NEW."order_country_code"
    FOR UPDATE;

    -- 处理新国家的首次订单
    IF next_seq IS NULL THEN
        next_seq := 1;
        INSERT INTO country_order_seq (country_code, current_seq)
        VALUES (NEW."order_country_code", next_seq);
    ELSE
        -- 更新流水号为新数值
        UPDATE country_order_seq
        SET current_seq = next_seq
        WHERE country_code = NEW."order_country_code";
    END IF;

    -- 格式化生成订单号
    NEW."order_no" = CONCAT(NEW."order_country_code", TO_CHAR(next_seq, '000000FM'));

    RETURN NEW;
END;
$$;

优势

  • 行级锁粒度极小,仅锁定对应国家的记录,对并发性能影响极低
  • 流水号存储独立,避免依赖订单表的COUNT操作(数据量大时COUNT性能差)
  • 逻辑清晰,易于维护和扩展新国家

方案二:按国家创建独立序列

如果国家数量少且固定,可为每个国家单独创建序列,触发器中根据国家代码调用对应序列生成流水号。

1. 为国家创建序列(示例:中国CN、美国US)

CREATE SEQUENCE seq_order_cn START 1 INCREMENT 1;
CREATE SEQUENCE seq_order_us START 1 INCREMENT 1;

2. 修改触发器函数

CREATE OR REPLACE FUNCTION func_gen_order_no()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
DECLARE
    next_seq INT;
BEGIN
    CASE NEW."order_country_code"
        WHEN 'CN' THEN next_seq := nextval('seq_order_cn');
        WHEN 'US' THEN next_seq := nextval('seq_order_us');
        -- 其他国家依次添加分支
        ELSE 
            RAISE EXCEPTION 'Unsupported country code: %', NEW."order_country_code";
    END CASE;

    NEW."order_no" = CONCAT(NEW."order_country_code", TO_CHAR(next_seq, '000000FM'));

    RETURN NEW;
END;
$$;

优势

  • 序列是PostgreSQL原生高并发自增机制,性能最优
  • 无需额外锁操作,数据库自动处理并发

劣势

  • 国家数量多或动态新增时,需频繁创建序列,维护成本高

注意事项

  • 保留order_no字段的唯一约束作为最后一道防线,极端情况也能避免脏数据
  • 方案一中的插入、更新操作已通过事务保证原子性,无需额外处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:40:17