Fra*_*rth 35 python database django locking race-condition
以下是具有潜在竞争条件的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()
Run Code Online (Sandbox Code Playgroud)
竞争条件应该是相当明显的:用户可以两次发出此请求,并且应用程序可能user = request.user同时执行,导致其中一个请求覆盖另一个请求.
假设函数calculate_points相对复杂,并且基于各种奇怪的东西进行计算,这些东西不能放在一个单独的update并且很难放入存储过程中.
所以这是我的问题:django可以使用哪种锁定机制来处理类似的情况?
Anu*_*yal 45
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()
Run Code Online (Sandbox Code Playgroud)
bju*_*nix 19
从Django 1.1开始,您可以使用ORM的F()表达式来解决这个特定问题.
from django.db.models import F
user = request.user
user.points = F('points') + calculate_points(user)
user.save()
Run Code Online (Sandbox Code Playgroud)
有关详细信息,请参阅文档:
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
数据库锁定是这里的方法.有计划为Django添加"select for update"支持(这里),但是现在最简单的方法是在开始计算分数之前使用原始SQL来更新用户对象.
当底层数据库(如Postgres)支持时,Django 1.4的ORM现在支持悲观锁定.请参阅Django 1.4a1发行说明.
你有很多方法来单线程这种东西.
一种标准方法是Update First.你做了一个更新,它将占用该行的独占锁; 然后做你的工作; 并最终提交更改.为此,您需要绕过ORM的缓存.
另一种标准方法是使用单独的单线程应用程序服务器,将Web事务与复杂计算隔离开来.
您的Web应用程序可以创建评分请求队列,生成单独的进程,然后将评分请求写入此队列.产卵可以放在Django中,urls.py所以它发生在web-app启动时.或者它可以放入单独的manage.py管理脚本中.或者,当尝试第一个评分请求时,可以"根据需要"完成.
您还可以使用Werkzeug创建一个单独的WSGI风格的Web服务器,它通过urllib2接受WS请求.如果此服务器具有单个端口号,则请求将按TCP/IP排队.如果您的WSGI处理程序有一个线程,那么,您已经实现了序列化单线程.这稍微可扩展,因为评分引擎是WS请求,可以在任何地方运行.
另一种方法是使用一些其他资源来获取和保存以进行计算.
数据库中的Singleton对象.可以使用会话ID更新唯一表中的单行以获取控制权; 使用会话ID更新None以释放控件.基本更新必须包含一个WHERE SESSION_ID IS NONE过滤器,以确保在其他人持有锁时更新失败.这很有趣,因为它本质上是无竞争的 - 它是单个更新 - 而不是SELECT-UPDATE序列.
可以在数据库外部使用花园种类的信号量.队列(通常)比低级信号量更容易使用.