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

Python 如何在Django中使用唯一检查避免竞争条件,python,mysql,django,postgresql,validation,Python,Mysql,Django,Postgresql,Validation,我有一个简单的模型: 班级邀请请求(models.Model): email=models.EmailField(最大长度=255,唯一性=True) 和一个简单的模型形式: 课堂邀请请求表单(forms.ModelForm): 类元: 模型=邀请请求 现在,假设我尝试以标准方式处理它: form=InvitationRequestForm(request.POST) 如果form.is_有效(): form.save() 存在竞争条件,因为验证执行一个简单的SELECT查询,以确定是否已

我有一个简单的模型:

班级邀请请求(models.Model):
email=models.EmailField(最大长度=255,唯一性=True)
和一个简单的模型形式:

课堂邀请请求表单(forms.ModelForm):
类元:
模型=邀请请求
现在,假设我尝试以标准方式处理它:

form=InvitationRequestForm(request.POST)
如果form.is_有效():
form.save()
存在竞争条件,因为验证执行一个简单的
SELECT
查询,以确定是否已存储此类电子邮件,如果一切正常,则继续执行
form.save()
行。如果有一个并发进程在完全相同的时刻执行相同的操作,则两个窗体都将进行验证,并且两个进程都将调用
form.save()
,因此一个进程将成功,另一个进程将失败,从而导致
IntegrityError

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

我希望表单对象中有一个标准错误,这样我就可以将其传递给模板,并将问题通知用户

我知道:

  • 我可以使用try/except包装所有内容,并手动将新错误添加到表单中
  • 我可以用
    SERIALIZABLE
    事务包装所有内容(在MySQL中,它会为每次选择执行下一个键锁定)
  • 我可以使用override
    模型。执行\u unique\u检查
    并 它使用
    选择\u进行\u更新
    (与MySQL配合使用,因为下一个键锁定)
  • 我可以获得表级独占锁

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

标准方法是不处理此问题,因为:

  • 在您的案例中,失败的概率接近于0
  • 故障的严重性非常低
  • 如果出于某种原因,你必须确保问题不会发生,那就靠你自己了

    我没有详细分析事件序列,但我认为使用可序列化隔离级别并没有真正的帮助,它只会导致在不同的位置引发
    IntegrityError
    (或
    DatabaseError

    覆盖
    模型。\执行\u独特的\u检查
    对我来说听起来是个坏主意,如果可能的话,你最好远离猴子补丁(在这里也是可能的)

    至于使用表锁来防止不太可能的错误。。。嗯,我不是一个大球迷,所以我也不能推荐

    对于一个类似的问题,这里有一个很好的答案:-我同意捕捉
    IntegrityError
    并重试可能是处理这个问题最简单、理智的方法


    编辑:我发现了这个:我同意@pid的答案。

    我同意托马斯齐林斯基的观点,即通常的做法是不担心这个问题。对于大多数用例来说,这不值得这么麻烦

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


    SERIALIZABLE
    在这里没有什么帮助。正如前面所说的,您必须准备好处理序列化失败,这意味着代码看起来与上面的代码基本相同。(不过,如果您没有强制数据库抛出异常的
    unique
    约束,这会有所帮助。)

    我不是Django的大用户,但类似的内容可能会有所帮助:@michael:Ha,很好。最近Javascript太多了。。。编辑。
    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