【问题标题】:Avoid Race Condition in Transaction避免事务中的竞争条件
【发布时间】:2015-09-10 10:57:11
【问题描述】:

我正在开发一个基于移动的购物应用程序。该应用程序的作用是,用户将钱存入他的帐户,然后再使用。 竞争条件是我试图避免的问题之一。这样用户帐户余额就不会被计算错误。

我使用的是mysql 5.5,php。

这是我想出的。

create table orders ( order_id int, user_id int, title varchar, item_price decimal, is_active int default null, constraint primary key (order_id), constraint unq unique (user_id, is_active) )

这个想法是对 user_id 和 is_active 设置唯一的约束,以便只能处理一个活动订单(存款或使用余额)。活动订单将 is_active 设置为 1。is_active 更新为时间戳,因此一旦订单完成,唯一约束将得到满足。存钱也是类似的逻辑。

这是购买项目的账户余额伪代码:

if  user has enough balance,
  start transaction
  insert into order with user_id, order_id, is_active=1
  update user balance = balance - item_price where balance >= item_price
  commit

if transaction success,
  update order set is_active= current_timestamp where user_id=, order_id=

这个逻辑有问题吗?

或者在没有唯一约束的情况下可以避免竞争条件: update user balance = balance - item_price where balance >= item_price

更新 1

我错过了一个会使事情变得更复杂的案例。详情如下:

当商品价格高于账户余额时,用户可以选择通过外部支付服务支付余额。

// first http request
try to cancel any previous active external payment by the same user
if user has enough balance,
    get a secure token from external payment service
    insert into order with user_id, order_id, is_active=1

// second http request
user paid and external payment service notifies my backend about the success payment. Then 
    start transaction
    update user balance = balance - balance_pay_amount where balance >= balance_pay_amount
    update order set is_active= current_timestamp where user_id=, order_id=
    commit

由于付款和帐户余额更新发生在一系列请求中。交易在这里行不通。

因此,我选择取消同一用户之前通过外部服务支付的任何有效订单,然后再创建另一个有效订单。这将产生一个副作用,即会减慢在短时间内提交许多无偿订单的用户。这可以作为额外的清理,以防任何现有的已放弃的活动订单阻止用户下新订单。

is_active 是防止竞争条件发生的保障措施。

【问题讨论】:

    标签: mysql race-condition


    【解决方案1】:

    is_active 标志不是必需的。在进行检查之前,您需要确保锁定用户的余额。

    start transaction 
    if  user has enough balance (lock the rows using a select query with FOR UPDATE)
      insert into order with user_id, order_id, is_active=1
      update user balance = balance - item_price where balance >= item_price
      commit
    else 
      rollback
      show some error or something
    

    这保证了当事务处于活动状态时,用户余额不能被另一个线程更改。它还保证if user has enough balance 只会针对目前没有活动交易的用户进行评估。

    【讨论】:

    • 我已经更新了我的问题以包含另一个案例,这将使 is_active 标志成为必要。请评论。谢谢。
    • 如果有半付订单的可能,您可以使用内部帐户支付尽可能多的费用,然后通过外部系统进行额外的支付。阻止用户同时处理多个订单只能部分解决问题,因为您系统的其他部分(可能在将来)可以修改他的帐户余额。在这个过程中你需要锁定的是余额而不是订单。
    猜你喜欢
    • 1970-01-01
    • 2015-01-30
    • 2010-09-25
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    • 2019-06-12
    • 1970-01-01
    相关资源
    最近更新 更多