Django中的竞争条件

50 投票
6 回答
27380 浏览
提问于 2025-04-15 12:26

这里有一个简单的例子,展示了一个django视图可能出现的竞争条件:

# myapp/views.py
from django.contrib.auth.models import User
from my_libs import calculate_points

def add_points(request):
    user = request.user
    user.points += calculate_points(user)
    user.save()

这个竞争条件应该很明显:一个用户可以发出两次相同的请求,而应用程序可能会同时执行 user = request.user 这行代码,这样就可能导致其中一个请求覆盖另一个请求的结果。

假设函数 calculate_points 比较复杂,它需要根据各种奇怪的东西进行计算,这些东西不能简单地放在一个 update 里,也很难放在一个存储过程里。

所以我想问的是:django有哪些锁定机制可以用来处理类似的情况呢?

6 个回答

8

在这里,数据库锁定是解决问题的好方法。目前有计划在Django中添加“选择以更新”的功能(可以在这里查看),但现在最简单的方法是使用原始SQL在开始计算分数之前先更新用户对象。


从Django 1.4开始,当底层数据库(比如Postgres)支持时,悲观锁定已经被Django的ORM支持了。详细信息可以查看Django 1.4a1版本说明

23

从Django 1.1开始,你可以使用ORM中的F()表达式来解决这个特定的问题。

from django.db.models import F

user = request.user
user.points  = F('points') + calculate_points(user)
user.save()

想了解更多细节,可以查看文档:

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

55

Django 1.4及以上版本支持一种叫做 select_for_update 的功能。在之前的版本中,你可以执行原始的SQL查询,比如 select ... for update。这个操作会根据你使用的数据库,锁定某一行数据,使得在事务结束之前,其他人无法对这行数据进行修改。在这段时间内,你可以随意对这行数据进行操作。比如:

from django.db import transaction

@transaction.commit_manually()
def add_points(request):
    user = User.objects.select_for_update().get(id=request.user.id)
    # you can go back at this point if something is not right 
    if user.points > 1000:
        # too many points
        return
    user.points += calculate_points(user)
    user.save()
    transaction.commit()

撰写回答