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

Node.js如何处理并发API请求?库存扣减竞态问题求解

Node.js中解决库存扣减并发冲突的最优方案

你的问题本质是并发场景下的库存超卖问题,核心原因是多个请求同时读取到相同的库存值,各自执行扣减后覆盖结果。最优解决方案分两类,优先级从高到低:

1. 数据库层面原子操作(最推荐)

直接通过数据库的原子更新语句,把库存读取和扣减合并为一个不可分割的操作,从根源避免并发冲突。

示例代码(以MySQL + mysql2库为例)

const mysql = require('mysql2/promise');

async function deductStock(productId, deductAmount) {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'your_password',
    database: 'your_db'
  });

  try {
    // 原子更新:仅当库存足够时才扣减,返回受影响的行数
    const [result] = await connection.execute(
      'UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?',
      [deductAmount, productId, deductAmount]
    );

    // 受影响行数为1则扣减成功,否则库存不足
    if (result.affectedRows === 1) {
      console.log('库存扣减成功');
      return true;
    } else {
      console.log('库存不足,扣减失败');
      return false;
    }
  } finally {
    await connection.end();
  }
}

// 调用示例
deductStock(1, 3); // 面包的productId假设为1

为什么这是最优?

  • 数据库原生支持原子性,无需额外引入依赖
  • 性能优于应用层锁,避免锁竞争带来的开销
  • 支持多实例部署,不会因为应用层集群失效

2. 应用层锁(补充方案)

如果业务场景需要在应用层做逻辑判断,可根据部署架构选择不同锁方案:

单实例部署:内存互斥锁

用async-mutex库实现内存级别的互斥,确保同一时间只有一个请求执行扣减逻辑:

const { Mutex } = require('async-mutex');

// 为每个商品创建独立的锁,避免全局锁影响性能
const productLocks = new Map();

async function deductStock(productId, deductAmount) {
  // 获取或创建当前商品的锁
  const mutex = productLocks.get(productId) || new Mutex();
  if (!productLocks.has(productId)) {
    productLocks.set(productId, mutex);
  }

  const release = await mutex.acquire();
  try {
    // 先查询当前库存
    const [rows] = await connection.execute('SELECT stock FROM products WHERE id = ?', [productId]);
    const currentStock = rows[0].stock;

    if (currentStock >= deductAmount) {
      await connection.execute('UPDATE products SET stock = ? WHERE id = ?', [currentStock - deductAmount, productId]);
      console.log('扣减成功');
      return true;
    } else {
      console.log('库存不足');
      return false;
    }
  } finally {
    release();
  }
}

多实例部署:Redis分布式锁

用redlock库实现跨实例的分布式锁,确保集群环境下的互斥:

const Redlock = require('redlock');
const Redis = require('ioredis');

const redis = new Redis({ host: 'localhost' });
const redlock = new Redlock([redis], {
  driftFactor: 0.01, // 时钟漂移容忍度
  retryCount: 3, // 重试次数
  retryDelay: 200 // 重试间隔
});

async function deductStock(productId, deductAmount) {
  const lockKey = `stock_lock:${productId}`;
  let lock;

  try {
    // 获取锁,有效期10秒(需扣减逻辑在有效期内完成)
    lock = await redlock.lock(lockKey, 10000);

    // 执行库存查询和扣减逻辑
    const [rows] = await connection.execute('SELECT stock FROM products WHERE id = ?', [productId]);
    const currentStock = rows[0].stock;

    if (currentStock >= deductAmount) {
      await connection.execute('UPDATE products SET stock = ? WHERE id = ?', [currentStock - deductAmount, productId]);
      console.log('扣减成功');
      return true;
    } else {
      console.log('库存不足');
      return false;
    }
  } catch (err) {
    console.log('获取锁失败,请求重试');
    return false;
  } finally {
    if (lock) await lock.unlock().catch(() => {});
  }
}

注意事项

  • 优先选择数据库原子操作,应用层锁仅作为特殊场景补充
  • 分布式锁需设置合理的过期时间,避免死锁
  • 不要在锁内执行耗时操作,否则会降低并发性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 22:15:49