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

Node.js PM2多集群环境下Mutex锁实现求助(PostgreSQL+Prisma)

解决PM2多集群下的库存超卖问题(PostgreSQL + Prisma)

单进程的async-mutex在PM2多集群环境下完全无效——每个Worker进程都是独立的,各自维护自己的Mutex实例,进程间无法共享锁状态,竞态条件依然存在。必须依赖数据库层面的锁机制来保证跨进程的并发安全,以下是基于PostgreSQL和Prisma的两种可靠实现方案:

方案一:悲观锁(排它行锁)

通过PostgreSQL的SELECT ... FOR UPDATE语法,在查询库存时对目标行加排它锁,确保同一时间只有一个事务能修改该记录,彻底避免竞态。

1. Prisma Schema 定义

假设你的商品模型包含库存字段:

model Product {
  id        Int     @id @default(autoincrement())
  name      String
  stock     Int     @default(0) // 库存数量
  // 其他业务字段...
}

2. 业务代码实现

async function buyItem(productId) {
  // 用Prisma事务包裹所有操作,保证原子性
  return await prisma.$transaction(async (tx) => {
    // 查询商品并加排它锁,阻塞其他事务修改该记录
    const product = await tx.product.findUnique({
      where: { id: productId },
      lock: { mode: 'update' } // 对应PostgreSQL的SELECT ... FOR UPDATE
    });

    if (!product || product.stock <= 0) {
      throw new Error('库存不足');
    }

    // 执行用户支付逻辑(替换为你的实际支付代码)
    await userPay();

    // 更新库存
    await tx.product.update({
      where: { id: productId },
      data: { stock: product.stock - 1 }
    });

    return { success: true, remainingStock: product.stock - 1 };
  });
}

说明

  • $transaction确保支付和库存更新要么全部成功,要么全部回滚,避免数据不一致
  • 排它锁会阻塞其他试图修改该商品的事务,直到当前事务提交/回滚,严格保证库存不会超卖
  • 适合高并发但对数据一致性要求极高的场景,缺点是极端高并发下可能出现锁等待,但属于可接受的性能损耗

方案二:乐观锁(版本号机制)

不主动加锁,而是通过版本号校验来检测并发冲突,冲突时自动重试。适合并发极高但冲突概率较低的场景,性能更优。

1. Prisma Schema 改造

添加版本号字段用于冲突校验:

model Product {
  id        Int     @id @default(autoincrement())
  name      String
  stock     Int     @default(0)
  version   Int     @default(1) // 乐观锁版本号,每次更新自增
  // 其他业务字段...
}

2. 业务代码实现

async function buyItem(productId, retryCount = 3) {
  try {
    return await prisma.$transaction(async (tx) => {
      const product = await tx.product.findUnique({
        where: { id: productId }
      });

      if (!product || product.stock <= 0) {
        throw new Error('库存不足');
      }

      // 执行用户支付逻辑
      await userPay();

      // 更新库存时校验版本号,只有版本匹配才允许更新
      const updatedProduct = await tx.product.update({
        where: {
          id: productId,
          version: product.version // 确保没有其他事务修改过该记录
        },
        data: {
          stock: product.stock - 1,
          version: product.version + 1 // 版本号自增
        }
      });

      // 如果更新返回为空,说明版本号不匹配,触发重试
      if (!updatedProduct) {
        throw new Error('库存更新冲突');
      }

      return { success: true, remainingStock: updatedProduct.stock };
    });
  } catch (error) {
    // 冲突时自动重试,最多重试3次
    if (error.message === '库存更新冲突' && retryCount > 0) {
      return await buyItem(productId, retryCount - 1);
    }
    throw error;
  }
}

说明

  • 乐观锁不会阻塞其他事务,只有当多个事务同时修改同一商品时才会触发冲突
  • 通过版本号校验确保只有"读取时的版本"和"更新时的版本"一致才会成功,避免超卖
  • 重试次数可根据业务场景调整,既保证成功率,又避免无限重试

为什么不推荐跨进程Mutex?

跨进程Mutex需要依赖共享内存、Redis等第三方服务,实现复杂度高,且依赖额外组件的可用性。而你已经在使用PostgreSQL,直接利用数据库原生的锁机制更可靠、更简单,不需要额外引入依赖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:44:56