【问题标题】:Node.js SQL race conditionsNode.js SQL 竞争条件
【发布时间】:2017-06-30 17:38:14
【问题描述】:

我想知道在执行 IO 时如何防止 NodeJS 中的竞争条件。我读了一点,每个人都坚持认为竞争条件是不可能的,因为 NodeJS 是单线程的。

让我们看看下面的伪代码:

async decreaseUserBalance(userId, amount) {
  const currentBalance = await sql.queryScalar('SELECT balance FROM user WHERE userid=?', [userId]);
  await sql.query('UPDATE user SET balance = ? WHERE userid = ?', [currentBalance - amount, userId]);
}

假设连接的数据库节点负载很重,并且需要一些时间来完成每个请求,并且每次用户单击某个项目上的购买按钮时都会调用此函数。

当用户开始向此按钮发送垃圾邮件或让机器人发送购买请求时会发生什么?据我了解,第一个请求将执行 SELECT 查询,并恢复执行。现在,由于用户正在向按钮发送垃圾邮件,下一个请求会进入,执行相同的功能。现在您有几个等待完成的 SELECT 语句。现在,几个 select 语句完成执行并执行回调,因此现在几个回调正在执行 UPDATE 语句,所有这些都具有相同的余额。这意味着如果起始余额为 5,则所有余额都会从最后一个已知余额减少 var amount。所以基本上不管你多久同时执行一次这个查询,第一批请求都会从 SELECT 查询中得到相同的值并将其更新为一些虚假的。

对不起,如果我的解释有点含糊,但我相信这是一个非常现实的问题,我怀疑很多人会考虑到这一点。

那么要解决这个问题,Node 是否支持互斥锁之类的东西?根据我的阅读,MongoDB 不支持表锁定,因此锁定只是 SQL 的一个选项。

编辑:

我知道我可以在 1 个查询中完成此示例,这样就可以解决它,但可以说这是不可能的。那你会怎么解决呢?

编辑 2:

好,我们来看这个例子:

  async tryBuyItem(userId, itemId, price) {

//Do we still have enough balance?

const currentBalance = await sql.queryScalar('SELECT balance FROM user WHERE userid=?', [userId]);

if (currentBalance >= price) {
  await sql.query('UPDATE user SET balance = balance - ? WHERE userid = ?', [price, userId]);
  await sql.query('INSERT INTO user_items (userid, itemid) VALUES (?, ?)', [userId, itemId]);

  return true;
} else {
  return false;
}
}

【问题讨论】:

  • 确实在那个级别你无法避免竞争条件。这就是 SQL 数据库具有事务性的原因!
  • @E_net4 交易究竟如何解决这个问题?即使这个函数被封装在一个事务中,你仍然可以异步执行几次,对吧?问题仍然存在
  • 数据库可以处理并发事务而不会失去一致性。如果您了解事务是 ACID,那么很清楚为什么同时请求不是问题。这不是 Node.js 的特殊问题。任何其他技术都可以通过足够的进程对数据库执行并发查询。
  • @user2073973,你的问题有点太理论化了。对于您发布的示例,ponury-kostek 的解决方案是正确的。对于更复杂的事情,也许您需要使用表锁定或事务。或者您可能需要稍微更改表的结构以允许更多原子操作。您描述的内容有解决方案,但没有通用的解决方案,这取决于您想要实现的目标。

标签: sql node.js race-condition


【解决方案1】:

你可以这样做

sql.query('UPDATE user SET balance = balance - ? WHERE userid = ?', [amount, userId]);

这里没有竞争条件,也没有使用任何事务。

【讨论】:

  • 我知道你可以做到这一点,但我确信在其他情况下这是不可能的。我想知道那该怎么办。
【解决方案2】:

如果这是一种支付方式,您必须更新和插入许多文档,并且您的服务器会在几分钟内获得很多点击,您可以使用 var q = async.queue(function (task, callback) { function(){} }, 5); 处理或将整个函数放入一个 async.waterfall 模块中一步一步执行。

https://caolan.github.io/async/docs.html#waterfall

【讨论】:

  • 这会以何种方式解决问题?如果许多客户端同时执行此操作,则 SELECT 和 INSERT 之间仍可能存在竞争条件,还是我遗漏了什么?
  • 说您像您一样预订出租车,可能有 100 个人在同一时间预订出租车。因此,我们将使用队列过程说消息队列类型的事情,我们将解决所有请求一个..对于选择和插入,我会先选择瀑布,然后再进行下一个回调插入。并且ofc将有来自前端和服务器的一些验证再次阻止相同的请求..到元素ddos
猜你喜欢
  • 1970-01-01
  • 2017-09-17
  • 1970-01-01
  • 1970-01-01
  • 2019-02-23
  • 2019-11-25
  • 2014-02-21
  • 2022-01-23
  • 2018-10-08
相关资源
最近更新 更多