简体   繁体   English

如何在Django中使用唯一检查来避免竞争条件

[英]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: 我知道:

  • I can wrap everything with try/except and add new error to my form manually 我可以使用try / except包装所有内容并手动将新错误添加到我的表单中
  • I can wrap everything with SERIALIZABLE transaction (in MySQL as it performs next key locking fo every select then) 我可以使用SERIALIZABLE事务包装所有内容(在MySQL中,因为它执行下一个键锁定每次选择)
  • I can use override 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一起使用)
  • I can acquire table-level exclusive lock 我可以获得表级独占锁

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: 标准方法是不处理这个,如:

  1. the probability of the failure in your case is close to 0; 你案件失败的概率接近于0;
  2. the severity of the failure is very low. 失败的严重程度非常低。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM