我的django项目中有这个accounts
模型,它存储了所有用户的帐户余额(可用资金)。几乎每个从用户帐户中扣除的都是金额检查,即检查用户是否有x金额或更多金额。如果是,则继续并扣除金额。
account = AccountDetails.objects.get(user=userid)
if int(account.amount) >= fare:
account.amount = account.amount-fare
account.save()
现在我想锁定第一个.get()
语句,以便可以避免竞争条件。用户发出两次请求,应用程序同时执行上述代码两次,导致其中一个请求覆盖另一个请求。
我发现select_for_update()完全符合我的要求。它将行锁定到事务结束。
account = AccountDetails.objects.select_for_update().get(user=userid)
但它仅在Django 1.4或更高版本中可用,而且我仍在使用Django 1.3,而现在无法完成移动到新版本。任何想法如何在我目前的Django版本中实现这一点?
答案 0 :(得分:1)
看起来你必须使用原始SQL。我查看了当前的代码,我认为自己尝试反向移植比编写SQL更麻烦。
account = AccountDetails.objects.raw(
"SELECT * FROM yourapp_accountdetails FOR UPDATE WHERE user = %s", [user.id]
)
为方便起见并保持代码干,您可以将其作为方法添加到AccountDetails模型或其他内容中。
class AccountDetails(models.Model):
@classmethod
def get_locked_for_update(cls, user):
return cls.objects.raw(
"SELECT * FROM yourapp_accountdetails FOR UPDATE WHERE user = %s", [user.id]
)
yourapp
是您在运行startapp时应用的名称。我假设您的AccountDetails与某种用户模型之间存在外键关系。
The current implementation of select_for_update on Django 1.5看起来像这样:
def select_for_update(self, **kwargs):
"""
Returns a new QuerySet instance that will select objects with a
FOR UPDATE lock.
"""
# Default to false for nowait
nowait = kwargs.pop('nowait', False)
obj = self._clone()
obj.query.select_for_update = True
obj.query.select_for_update_nowait = nowait
return obj
这是非常简单的代码。只需在查询对象上设置几个标志即可。但是,执行查询时,这些标志不会有任何意义。所以在某些时候你需要编写原始SQL。此时,您只需要在AccountDetails模型上进行查询。所以现在就把它放在那里。也许以后你会在另一个型号上需要它。那时你必须决定如何在模型之间共享代码。