[英]Avoid Race Condition in Transaction
I am developing a mobile based shopping app. 我正在开发基于移动的购物应用程序。 What the app does is, user deposits money in his account and spend it later.
该应用程序的作用是,用户将钱存入他的帐户中,以后再用。 Race condition is one of the problem I am trying to avoid.
种族状况是我要避免的问题之一。 such that user account balance won't be miscalculated.
这样就不会错误地计算用户帐户余额。
I am using mysql 5.5, php. 我正在使用mysql 5.5,php。
Here is what I have come up. 这是我提出的。
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) )
The idea is to set unique constraint on user_id and is_active so that only one active order(deposit money or use balance) can be processed. 想法是对user_id和is_active设置唯一的约束,以便只能处理一个有效订单(存款或使用余额)。 active order will have
is_active
set to 1. is_active
is updated to a timestamp so the unique constraint will be satisfied once the order is completed. 有效订单的
is_active
设置为is_active
更新为时间戳,因此一旦订单完成,唯一约束将得到满足。 Deposit money is similar logic. 存钱是类似的逻辑。
Here is the pseudo code for purchase item with account balance: 这是带有帐户余额的购买商品的伪代码:
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=
Is there any problem with this logic? 这个逻辑有什么问题吗?
Or the race condition can be avoided without the unique constraint with this line: update user balance = balance - item_price where balance >= item_price
或者使用此行,可以在没有唯一约束的情况下避免竞争条件:
update user balance = balance - item_price where balance >= item_price
UPDATE 1 更新1
I have missed a case that will make things more complicated. 我错过了一个使情况变得更加复杂的案例。 Here is the detail:
这是详细信息:
User can choose to pay the remaining via an external payment service when an item price is greater than his account balance. 当物料价格大于其帐户余额时,用户可以选择通过外部支付服务来支付剩余款项。
// 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
Since the payment and account balance update happen in a span of requests. 由于付款和帐户余额更新是在多个请求中发生的。 transaction along won't work here.
交易在这里不起作用。
So I choose to cancel any previous active order paid via external service by the same user before creating another active order. 因此,我选择在创建另一个有效订单之前取消同一用户通过外部服务支付的任何先前的有效订单。 This will have a side effect of slowing down user who submit many orders without pay in a short period of time.
这将产生副作用,使用户在短时间内无偿提交许多订单的用户放慢速度。 This serves as additional cleanup in case any existing abandoned active order prevent user making new order.
如果任何现有的已放弃活动订单阻止用户下达新订单,这可以作为额外的清理工作。
is_active
is the safeguard to prevent race condition from happening. is_active
是防止争用情况发生的保障措施。
The is_active
flag is not necessary. is_active
标志不是必需的。 You need to make sure that you lock the user's balance before you do the check. 在执行检查之前,您需要确保锁定用户的余额。
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
This guarantees that the user balance can't be changed by another thread while the transaction is active. 这样可以确保在事务处于活动状态时,其他线程无法更改用户余额。 It also guarantees that the
if user has enough balance
will only be evaluated for a user which does not have a transaction active at the moment. 它还保证仅针对当前没有活跃交易的用户评估
if user has enough balance
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.