这是一个简单的例子的一个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、处理情况与此类似的?

有帮助吗?

解决方案

Django 1.4+支持 select_for_update ,在早期版本中,您可以执行原始SQL查询,例如 select ... for update ,根据底层数据库将锁定任何更新的行,您可以使用该行执行任何操作,直到事务结束。 e.g。

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()

其他提示

从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 /模型/表达式/#django.db.models.F

数据库锁定是这里的方法。有计划添加“select for update”;支持Django(此处),但目前最简单的方法是使用原始SQL进行更新在开始计算分数之前的用户对象。


当底层数据库(如Postgres)支持它时,Django 1.4的ORM现在支持悲观锁定。请参阅 Django 1.4a1发行说明

你有很多方法来单线这样的事情。

一个标准的办法是 更新的第一个.你做的更新,这将抓住一个独特锁行;然后做你的工作;最后提交的变化。对于这项工作,你需要旁的奥姆的缓存。

另一个标准做法是要有一个独立的、单程应用程序服务器将网交易的复杂的计算。

  • 你的网络应用程序可以创建一个队的得分要求,产生一个单独的进程,然后编写的评分要求这队列中。在产卵可以放在Django urls.py 那么它发生于网络的应用程序的启动。或者,它可以放入独立的 manage.py 管理脚本。或者这是可以做到"为所需要的"当第一个得分的请求是企图。

  • 你还可以创建一个独立的过味网服务器使用,其接受WS请求通过urllib2.如果你有一个单一的口号用于这种服务器,请求进行排队的TCP/IP。如果你过处理程序具有一个线程,然后,你已经实现化单一的线程。这个稍微更多的可扩缩的,因为分动机是WS请求,并且可以运行。

然而另一种做法是有一些其他资源,必须获得并保持做的计算。

  • 一个单独目的数据库。一个单列在一个独特的表可以更新一届会议ID抓住的控制;更新与session ID None 释放控制。必要的更新以包含一个 WHERE SESSION_ID IS NONE 过滤,以确保更新失败的时候锁举行的其他人。这是有趣,因为它是固有的种族免费--这是一个单一的更新--不是一个选择更新序列。

  • 一个花园-各种信号可以使用外部数据库。排队(一般)工作更容易比一个低级别的信号。

这可能会使您的情况过于简单,但仅仅更换JavaScript链接呢?换句话说,当用户点击链接或按钮将请求包装在JavaScript函数中时,该函数立即禁用/“灰化”。链接并用“正在加载...”替换文本。或“提交请求......”信息或类似的东西。这对你有用吗?

现在,你必须使用:

Model.objects.select_for_update().get(foo=bar)
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top