[英]How to avoid race condition with unique checks in Django
I have a simple model: 我有一个简单的模型:
class InvitationRequest(models.Model):
email = models.EmailField(max_length=255, unique=True)
And a simple model form: 一个简单的模型形式:
class InvitationRequestForm(forms.ModelForm):
class Meta:
model = InvitationRequest
Now, assuming that I attempt to process it in a standard way: 现在,假设我尝试以标准方式处理它:
form = InvitationRequestForm(request.POST)
if form.is_valid():
form.save()
There is a race condition because validation performs a simple SELECT
query to determine whether or not such email is already stored, and if everything is fine then it proceeds to form.save()
line. 存在竞争条件,因为验证执行简单的SELECT
查询以确定是否已存储此类电子邮件,如果一切正常,则继续执行form.save()
行。 If there is a concurrent process that does the same at exactly the same moment, then both forms will validate and both processes will call form.save()
thus one will succeed and the other will fail causing an IntegrityError
. 如果有一个并发进程在同一时刻执行相同操作,则两个表单都将验证,并且两个进程都将调用form.save()
因此一个将成功,另一个将失败导致IntegrityError
。
What is the standard way to handle this? 处理这个问题的标准方法是什么?
I want to have a standard error in the form object so I can pass it on to the template and notify user about the problem. 我希望在表单对象中有一个标准错误,因此我可以将其传递给模板并通知用户该问题。
I know that: 我知道:
SERIALIZABLE
transaction (in MySQL as it performs next key locking fo every select then) 我可以使用SERIALIZABLE
事务包装所有内容(在MySQL中,因为它执行下一个键锁定每次选择) Model._perform_unique_checks
and make it use select_for_update
(works with MySQL because of next key locking) 我可以使用覆盖Model._perform_unique_checks
并使用select_for_update
(由于下一次键锁定而与MySQL一起使用) None of these solutions is appealing, also I am using PostgreSQL which differs from MySQL in this area. 这些解决方案都没有吸引力,我也使用PostgreSQL,它与MySQL在这个领域不同。
The standard way is to NOT handle this, as: 标准方法是不处理这个,如:
If, for some reason, you have to be sure that the problem won't happen, you are on your own. 如果出于某种原因,你必须确定问题不会发生,那么你就是靠自己。
I haven't analyzed the sequence of events in detail but I think that using the SERIALIZABLE isolation level won't really help, it will only cause IntegrityError
(or DatabaseError
) to be raised in a different place. 我没有详细分析事件的顺序,但我认为使用SERIALIZABLE隔离级别实际上没有帮助,它只会导致在不同的地方引发IntegrityError
(或DatabaseError
)。
Overriding Model._perform_unique_checks
sounds to me like a bad idea, you better stay away from monkey patching if possible (and here it is possible). 覆盖Model._perform_unique_checks
听起来像是一个坏主意,如果可能的话,你最好远离猴子补丁(这里有可能)。
As for using the table lock to prevent unlikely errors... Well, I'm not a big fan so I cannot recommend that either. 至于使用表锁来防止不可能出现的错误......好吧,我不是一个大粉丝所以我也不能推荐。
Here's a nice answer to a similar question: https://stackoverflow.com/a/3523439/176186 - I concur that catching IntegrityError
and retrying is probably the easiest sane way to deal with the problem. 这是一个类似问题的一个很好的答案: https : //stackoverflow.com/a/3523439/176186 - 我同意捕获IntegrityError
和重试可能是处理问题最简单明智的方法。
EDIT: I found this: Symfony2 - how to recover from unique constraint error after form submission? 编辑:我发现这个: Symfony2 - 如何在表单提交后从唯一约束错误中恢复? and I agree with @pid's answer. 我同意@pid的回答。
I agree with Tomasz Zielinski that common practice is to not worry about this. 我同意Tomasz Zielinski的观点,即通常的做法是不要担心这一点。 For most use cases it's just not worth the trouble. 对于大多数用例来说,这不值得。
If it is important, the best way is probably with optimistic concurrency. 如果它很重要,最好的方法可能是乐观并发。 In this case it might look like (untested): 在这种情况下,它可能看起来像(未经测试):
from django.forms.util import ErrorList
def handle_form(request)
form = InvitationRequestForm(request.POST)
try:
if form.is_valid():
form.save()
return HttpResponseRedirect(...) # redirect to success url
except IntegrityError:
form._errors['email'] = ErrorList()
form._errors['email'].append('Error msg')
return render(...) # re-render the form with errors
SERIALIZABLE
won't really help here. SERIALIZABLE
在这里不会有所帮助。 As the PostgreSQL documentation makes clear, you have to be prepared to handle serialization failures, which means that the code would look pretty much the same as above. 正如PostgreSQL文档所表明的那样,您必须准备好处理序列化失败,这意味着代码看起来与上面几乎相同。 (It would help, though, if you didn't have the unique
constraint forcing the database to throw an exception.) (但是,如果你没有强制数据库抛出异常的unique
约束,那将会有所帮助。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.