Python 为django 1.3中的更新类功能选择_,以避免竞争条件

Python 为django 1.3中的更新类功能选择_,以避免竞争条件,python,django,django-queryset,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() 现在我想在第一个.

我在django项目中有一个
accounts
模型,它存储所有用户的帐户余额(可用资金)。几乎每次从用户帐户扣除之前都会进行金额检查,即检查用户是否有x笔或更多金额。如果是,则继续并扣除金额

account = AccountDetails.objects.get(user=userid)
if int(account.amount) >= fare:
    account.amount = account.amount-fare
    account.save()
现在我想在第一个
.get()
语句中加上一个锁,这样就可以避免竞争条件。用户发出两次请求,应用程序同时执行上述代码两次,导致其中一个请求覆盖另一个请求

我发现这正是我想要的。它将锁定行,直到事务结束

account = AccountDetails.objects.select_for_update().get(user=userid)

但它只在Django1.4或更高版本中可用,我仍在使用Django1.3,现在无法升级到新版本。在我目前的Django版本中,有什么想法可以实现这一点吗?

看起来您必须使用原始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与某种用户模型之间存在外键关系

看起来像这样:

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

+1建议将其作为方法添加到模型:)但我可以避免同时使用原始sql查询吗?请参阅更新的答案。我认为答案是否定的。如果没有从Django的更高版本中移植大量内容,就不能这样做。@ayechedee谢谢你的解释。我想知道如果我将
get\u locked\u for\u update()
实现为类方法而不是实例方法,会不会更好。在类上调用它会更有意义,比如:
AccountDetails.get\u locked\u for\u update(user)
而不是
AccountDetails.objects.get(user=userid)。get\u locked\u for\u update()
。对吧?是的,那会更有意义。我们可以添加
classmethod
decorator并传入用户。我有一个关于这个解决方案的后续问题。请仔细检查一下,看看你是否能帮上忙。