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

多系统并发curl POST批量录入订单时出现重复发票编号问题咨询

问题根源

你的所有现有实现都存在竞态条件问题:多个并发请求会同时查询到相同的最新订单编号,各自加1后生成了重复的OrderNumber,这是重复插入的核心原因。
你之前的方案失效原因如下:

  • PHP端处理逻辑:先提交事务再判断订单号是否存在,此时其他并发请求可能已经插入了相同编号,即使后续重新生成更新,仍然存在并发冲突概率,且效率极低。
  • 触发器逻辑:BEFORE INSERT中查询最新编号的语句没有加锁,多个并发插入的触发器会同时读取到同一个LastOrderNo,自然生成重复编号。你添加唯一索引后插入直接报错,本质就是这个并发冲突被唯一索引拦截了。

可行解决方案

方案1:MySQL独立序列表(最稳定,推荐)

利用MySQL自增主键的原子性完全规避竞态,实现成本低、稳定性高:

  1. 新建专门用于生成订单序号的序列表:
CREATE TABLE order_sequence (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
) ENGINE=InnoDB;
  1. 修改插入触发器逻辑,用序列表的自增ID作为订单号的递增序列:
CREATE TRIGGER `Insert_OrderNumber` BEFORE INSERT ON `tOrder`
FOR EACH ROW BEGIN
    -- 插入序列表拿到全局唯一自增ID
    INSERT INTO order_sequence VALUES (NULL);
    SET @seq = LPAD(2245444, 6, '0');
    -- 前缀可根据你的业务规则调整,比如示例中的ULEN、UCMC前缀、年份拼接等逻辑
    SET NEW.OrderNumber = CONCAT('UABC', DATE_FORMAT(NOW(), '%y'), @seq);
END
  1. 给tOrder表的OrderNumber字段添加唯一索引作为兜底校验,避免极端场景出错。

方案2:PHP端加分布式锁

如果有Redis可用,可以通过分布式锁保证同一时间只有一个请求生成订单号:

foreach($Orders as $order)
{
    $lockKey = 'order_number_generate_lock';
    // 自旋抢锁,锁过期时间设为10秒足够处理插入逻辑
    while (!$redis->set($lockKey, 1, ['nx', 'ex' => 10])) {
        usleep(10000); // 间隔10ms重试
    }
    try {
        $insArr =[
            'OrderNumber' => $this->GenerateOrderNo(),
            'CreatedBy'   => 1,    
        ];
        $this->db->insert('tOrder',$insArr);
    } finally {
        // 执行完释放锁
        $redis->del($lockKey);
    }
}

方案3:MySQL行级锁实现原子生成

如果不想新增表也不想依赖Redis,可以在查询最新编号时加排他锁,放在事务内执行:

foreach($Orders as $order)
{
    $this->db->trans_begin();
    try {
        // 加FOR UPDATE排他锁,其他事务必须等当前事务提交后才能查询最新编号
        $lastNo = $this->db->query("SELECT Right(OrderNumber,6) as no FROM tOrder ORDER BY OrderUID DESC LIMIT 1 FOR UPDATE")->row()->no;
        $newNo = str_pad($lastNo + 1, 6, '0', STR_PAD_LEFT);
        // 拼接前缀逻辑按业务需求调整
        $orderNo = 'UABC' . date('y') . $newNo;
        $insArr =[
            'OrderNumber' => $orderNo,
            'CreatedBy'   => 1,    
        ];
        $this->db->insert('tOrder',$insArr);
        $this->db->trans_commit();
    } catch (Exception $e) {
        $this->db->trans_rollback();
        // 可按需添加重试逻辑
    }
}

注意该方案需要数据库事务隔离级别至少为READ COMMITTED,否则行锁可能不生效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 22:54:03