我正在开发基于移动的购物应用。该应用程序的作用是,用户将钱存入他的帐户并稍后使用。 竞争条件是我试图避免的问题之一。这样用户帐户余额不会被误算。
我正在使用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
是防止发生种族情况的保障措施。
答案 0 :(得分:0)
不需要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
仅针对目前没有活动交易的用户进行评估。