select_for_update在django 1.3中的一些功能,以避免竞争条件

时间:2013-07-15 09:41:11

标签: python django django-queryset

我的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版本中实现这一点?

1 个答案:

答案 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模型上进行查询。所以现在就把它放在那里。也许以后你会在另一个型号上需要它。那时你必须决定如何在模型之间共享代码。