Django中的竞争条件
这里有一个简单的例子,展示了一个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/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()