0

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

4

1 回答 1

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 与某种用户模型有外键关系。

Django 1.5 上 select_for_update 的当前实现如下所示:

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

于 2013-07-15T10:09:54.980 回答