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

时间:2014-09-06 17:17:51

标签: python mysql django postgresql validation

我有一个简单的模型:

class InvitationRequest(models.Model):
    email = models.EmailField(max_length=255, unique=True)

一个简单的模型形式:

class InvitationRequestForm(forms.ModelForm):
    class Meta:
        model = InvitationRequest

现在,假设我尝试以标准方式处理它:

form = InvitationRequestForm(request.POST)
if form.is_valid():
    form.save()

存在争用条件,因为验证会执行简单的SELECT查询以确定是否已存储此类电子邮件,如果一切正常,则会转到form.save()行。如果有一个并发进程在同一时刻执行相同操作,则两个表单都将验证,并且两个进程都将调用form.save(),因此一个将成功,另一个将失败导致IntegrityError

处理此问题的标准方法是什么?

我希望在表单对象中出现标准错误,以便将其传递给模板并通知用户该问题。

我知道:

  • 我可以使用try / except包装所有内容并手动向表单添加新错误
  • 我可以使用SERIALIZABLE事务包装所有内容(在MySQL中,因为它会在每次选择时执行下一个键锁定)
  • 我可以使用覆盖Model._perform_unique_checks和make 它使用select_for_update(由于下一个键锁定而与MySQL一起工作)
  • 我可以获得表级独占锁

这些解决方案都没有吸引力,我也使用的PostgreSQL与MySQL在这方面不同。

2 个答案:

答案 0 :(得分:8)

标准方法是不处理这个问题,如:

  1. 您案件中失败的概率接近于0;
  2. 失败的严重程度非常低。
  3. 如果出于某种原因,你必须确定问题不会发生,那么你就是靠自己。

    我还没有详细分析事件的顺序,但我认为使用SERIALIZABLE隔离级别确实没有帮助,它只会导致IntegrityError(或DatabaseError)在不同的地方长大。

    覆盖Model._perform_unique_checks听起来像是一个坏主意,如果可能的话,你最好远离猴子补丁(这里可能)。

    至于使用表锁来防止不可能出现的错误......好吧,我不是一个大粉丝所以我不能推荐它。

    这是一个类似问题的一个很好的答案:https://stackoverflow.com/a/3523439/176186 - 我同意,抓住IntegrityError并重试可能是处理问题的最简单明智的方法。

    编辑:我发现了这个:Symfony2 - how to recover from unique constraint error after form submission?我同意@pid的答案。

答案 1 :(得分:3)

我同意Tomasz Zielinski的观点,即通常的做法是不要担心这一点。对于大多数用例来说,这不值得麻烦。

如果 重要,那么最好的方法可能是乐观并发。在这种情况下,它可能看起来像(未经测试):

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在这里不会有任何帮助。正如PostgreSQL documentation明确指出的那样,您必须准备好处理序列化失败,这意味着代码看起来与上面几乎相同。 (但是,如果你没有强制数据库抛出异常的unique约束,帮助。)