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

使用.map()批量生成订单ID重复,异步DB更新未等待问题

生成唯一订单ID的并发问题及解决方法

问题背景

需要为cartData中的每个商品生成唯一orderId,但使用.map()结合Promise.all迭代时,.map()不会等待updateOrderNo()完成数据库的orderNo值更新,导致第二次迭代获取到的orderNo与第一次相同,生成重复订单ID。

相关代码

createOrder 函数

export const createOrder = () => {
    const result = new Promise(async (resolve, reject) => {
    try {
        const cartData = {
        cartId: 1234,
        items: [
            { name: "One" },
            { name: "Two" }
          ]
        };

        await Promise.all(cartData.items.map(async (item, index) => {
           const orderNumber = await getNewOrderNo();
           console.log("index", index, "orderNumber", orderNumber);

           const OrderData = {
              orderId: orderNumber,
              name: item.name,
              subTotalAmount: 1000,
              discountAmount: 0,
              shippingChargeAmount: 10,
              totalAmount: 1010,
           };

           const newOrderData = new Order(OrderData);
           const addedOrder = await newOrderData.save();
           console.log(`addedOrder - index ${index} ::::`, addedOrder.orderId);

           if (addedOrder) {
               const updatedOrderNo = await updateOrderNo();
           }
        }));

           resolve("success");
    } catch (error) {
           console.error('createOrder catch error', error);
           reject(new Error(error));
    }
   });

    return result;
};

getNewOrderNo 函数

export const getNewOrderNo = async () => {
    try {
        const order = await Setting.findOne({ key: "order_no" });
        var str = "" + (order.value + 1);
        var pad = "0000";
        var ans = pad.substring(0, pad.length - str.length) + str;

        return order.prefix + "-" + ans;
    } catch (error) {
        console.error('getOrderNo catch error :::', error);
    }
};

updateOrderNo 函数

export const updateOrderNo = async () => {
    const result = new Promise(async (resolve, reject) => {
    try {
            const order = await Setting.findOne({ key: "order_no" });
        const newOrderNumber = order.value + 1;

        const updateOrderNumber = await Setting.updateOne(
            { key: "order_no" },
            { $set: { value: newOrderNumber } }
        );

        if (updateOrderNumber.nModified == 1) {
             resolve(newOrderNumber);
        } else {
             console.error(`order_no is not updated in db`);
        }
    } catch (error) {
        console.error('updateOrderNo catch error :::', error);
        reject('updateOrderNo failed', error);
    }
    });

    return result;
};

MongoDB Setting集合数据

{
  "_id":  "607840696826c3ca20a491fb",
  "key": "order_no",
  "value": 1000,
  "prefix": "ABC",
}

当前控制台输出

index 0 orderNumber ABC-1001
index 1 orderNumber ABC-1001

::: addedOrder - index 0 :::: ABC-1001
::: addedOrder - index 1 :::: ABC-1001

期望控制台输出

index 0 orderNumber ABC-1001
index 1 orderNumber ABC-1002

::: addedOrder - index 0 :::: ABC-1001
::: addedOrder - index 1 :::: ABC-1002

问题原因

Promise.all会同时触发所有.map()中的异步函数,两个请求几乎同时调用getNewOrderNo,此时数据库中的orderNo还未被第一个请求更新,导致两个请求都读取到value=1000,生成相同的ABC-1001订单号,之后才各自执行更新操作,最终出现重复ID。

解决方案

核心优化:原子操作替代拆分读写

将getNewOrderNo和updateOrderNo合并为一个原子操作,使用MongoDB的findOneAndUpdate方法,保证读取订单号和更新订单号的操作是原子性的,避免并发场景下的重复读取。

优化后的原子操作函数

export const getAndUpdateOrderNo = async () => {
  try {
    // 原子操作:找到文档并自增value,返回更新前的文档
    const order = await Setting.findOneAndUpdate(
      { key: "order_no" },
      { $inc: { value: 1 } }, // 原子自增,避免并发冲突
      { new: false } // 返回更新前的文档,用于计算新订单号
    );

    if (!order) {
      throw new Error("未找到order_no配置项");
    }

    const nextValue = order.value + 1;
    const str = "" + nextValue;
    const pad = "0000";
    const ans = pad.substring(0, pad.length - str.length) + str;

    return order.prefix + "-" + ans;
  } catch (error) {
    console.error('getAndUpdateOrderNo 错误 :::', error);
    throw error;
  }
};

方案一:保持并行处理(原子操作支持并发)

修改createOrder函数,直接使用原子操作函数,此时即使并行处理也不会生成重复订单ID:

export const createOrder = async () => {
  try {
    const cartData = {
      cartId: 1234,
      items: [
        { name: "One" },
        { name: "Two" }
      ]
    };

    await Promise.all(cartData.items.map(async (item, index) => {
      const orderNumber = await getAndUpdateOrderNo();
      console.log("index", index, "orderNumber", orderNumber);

      const OrderData = {
        orderId: orderNumber,
        name: item.name,
        subTotalAmount: 1000,
        discountAmount: 0,
        shippingChargeAmount: 10,
        totalAmount: 1010,
      };

      const newOrderData = new Order(OrderData);
      const addedOrder = await newOrderData.save();
      console.log(`addedOrder - index ${index} ::::`, addedOrder.orderId);
    }));

    return "success";
  } catch (error) {
    console.error('createOrder 错误', error);
    throw new Error(error);
  }
};

方案二:串行处理(严格按顺序生成订单号)

如果需要严格按照商品顺序生成递增的订单号,可以改用for...of循环串行处理:

export const createOrder = async () => {
  try {
    const cartData = {
      cartId: 1234,
      items: [
        { name: "One" },
        { name: "Two" }
      ]
    };

    for (const [index, item] of cartData.items.entries()) {
      const orderNumber = await getAndUpdateOrderNo();
      console.log("index", index, "orderNumber", orderNumber);

      const OrderData = {
        orderId: orderNumber,
        name: item.name,
        subTotalAmount: 1000,
        discountAmount: 0,
        shippingChargeAmount: 10,
        totalAmount: 1010,
      };

      const newOrderData = new Order(OrderData);
      const addedOrder = await newOrderData.save();
      console.log(`addedOrder - index ${index} ::::`, addedOrder.orderId);
    }

    return "success";
  } catch (error) {
    console.error('createOrder 错误', error);
    throw new Error(error);
  }
};

说明

  • 原代码的问题在于读取和更新订单号是两个独立操作,并发时会出现竞态条件。
  • 使用findOneAndUpdate的$inc操作符可以保证数据库层面的原子性,无论多少并发请求,每次获取的订单号都是唯一递增的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 16:35:00