python Django 中的竞争条件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1030270/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Race conditions in django
提问by Fragsworth
Here is a simple example of a django view with a potential 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()
The race condition should be fairly obvious: A user can make this request twice, and the application could potentially execute user = request.user
simultaneously, causing one of the requests to override the other.
竞争条件应该相当明显:用户可以发出两次此请求,并且应用程序可能user = request.user
同时执行,导致其中一个请求覆盖另一个请求。
Suppose the function calculate_points
is relatively complicated, and makes calculations based on all kinds of weird stuff that cannot be placed in a single update
and would be difficult to put in a stored procedure.
假设这个函数calculate_points
比较复杂,并且基于各种不能放在一个单一update
并且很难放在存储过程中的奇怪东西进行计算。
So here is my question: What kind of locking mechanisms are available to django, to deal with situations similar to this?
所以这是我的问题:django 可以使用什么样的锁定机制来处理类似的情况?
回答by Anurag Uniyal
Django 1.4+ supports select_for_update, in earlier versions you may execute raw SQL queries e.g. select ... for update
which depending on underlying DB will lock the row from any updates, you can do whatever you want with that row until the end of transaction. e.g.
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()
回答by bjunix
As of Django 1.1 you can use the ORM's F() expressions to solve this specific problem.
从 Django 1.1 开始,您可以使用 ORM 的 F() 表达式来解决这个特定问题。
from django.db.models import F
user = request.user
user.points = F('points') + calculate_points(user)
user.save()
For more details see the documentation:
有关更多详细信息,请参阅文档:
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
回答by zooglash
Database locking is the way to go here. There are plans to add "select for update" support to Django (here), but for now the simplest would be to use raw SQL to UPDATE the user object before you start to calculate the score.
数据库锁定是这里的方法。有计划向 Django(此处)添加“选择更新”支持,但目前最简单的方法是在开始计算分数之前使用原始 SQL 更新用户对象。
Pessimistic locking is now supported by Django 1.4's ORM when the underlying DB (such as Postgres) supports it. See the Django 1.4a1 release notes.
当底层数据库(如 Postgres)支持悲观锁时,Django 1.4 的 ORM 现在支持它。请参阅Django 1.4a1 发行说明。
回答by S.Lott
You have many ways to single-thread this kind of thing.
你有很多方法可以单线程处理这种事情。
One standard approach is Update First. You do an update which will seize an exclusive lock on the row; then do your work; and finally commit the change. For this to work, you need to bypass the ORM's caching.
一种标准方法是先更新。您进行了一个更新,它将在该行上获得一个排他锁;然后做你的工作;最后提交更改。为此,您需要绕过 ORM 的缓存。
Another standard approach is to have a separate, single-threaded application server that isolates the Web transactions from the complex calculation.
另一种标准方法是拥有一个单独的、单线程的应用程序服务器,将 Web 事务与复杂的计算隔离开来。
Your web application can create a queue of scoring requests, spawn a separate process, and then write the scoring requests to this queue. The spawn can be put in Django's
urls.py
so it happens on web-app startup. Or it can be put into separatemanage.py
admin script. Or it can be done "as needed" when the first scoring request is attempted.You can also create a separate WSGI-flavored web server using Werkzeug which accepts WS requests via urllib2. If you have a single port number for this server, requests are queued by TCP/IP. If your WSGI handler has one thread, then, you've achieved serialized single-threading. This is slightly more scalable, since the scoring engine is a WS request and can be run anywhere.
您的 Web 应用程序可以创建一个评分请求队列,生成一个单独的进程,然后将评分请求写入该队列。spawn 可以放在 Django 中,
urls.py
所以它会在 web 应用程序启动时发生。或者它可以放入单独的manage.py
管理脚本中。或者可以在尝试第一个评分请求时“根据需要”完成。您还可以使用 Werkzeug 创建一个单独的 WSGI 风格的 Web 服务器,它通过 urllib2 接受 WS 请求。如果此服务器只有一个端口号,则请求将按 TCP/IP 排队。如果您的 WSGI 处理程序只有一个线程,那么您就实现了序列化单线程。这稍微更具可扩展性,因为评分引擎是一个 WS 请求并且可以在任何地方运行。
Yet another approach is to have some other resource that has to be acquired and held to do the calculation.
另一种方法是拥有一些必须获得和持有的其他资源来进行计算。
A Singleton object in the database. A single row in a unique table can be updated with a session ID to seize control; update with session ID of
None
to release control. The essential update has to include aWHERE SESSION_ID IS NONE
filter to assure that the update fails when the lock is held by someone else. This is interesting because it's inherently race-free -- it's a single update -- not a SELECT-UPDATE sequence.A garden-variety semaphore can be used outside the database. Queues (generally) are easier to work with than a low-level semaphore.
数据库中的单例对象。可以使用会话 ID 更新唯一表中的单行以获取控制权;使用会话 ID 更新
None
以释放控制。基本更新必须包含一个WHERE SESSION_ID IS NONE
过滤器,以确保当其他人持有锁时更新失败。这很有趣,因为它本质上是无竞争的——它是一个单一的更新——而不是一个 SELECT-UPDATE 序列。可以在数据库之外使用花园式信号量。队列(通常)比低级信号量更容易使用。
回答by Wayne Koorts
This may be oversimplifying your situation, but what about just a JavaScript link replacement? In other words when the user clicks the link or button wrap the request in a JavaScript function which immediately disables / "greys out" the link and replaces the text with "Loading..." or "Submitting request..." info or something similar. Would that work for you?
这可能过于简单化了您的情况,但是仅替换 JavaScript 链接呢?换句话说,当用户单击链接或按钮时,将请求包装在一个 JavaScript 函数中,该函数立即禁用/“变灰”链接并将文本替换为“正在加载...”或“提交请求...”信息或其他内容相似的。这对你有用吗?
回答by dingyaguang117
Now, you must use:
现在,您必须使用:
Model.objects.select_for_update().get(foo=bar)