我有一个简单的模型:
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
。
处理此问题的标准方法是什么?
我希望在表单对象中出现标准错误,以便将其传递给模板并通知用户该问题。
我知道:
SERIALIZABLE
事务包装所有内容(在MySQL中,因为它会在每次选择时执行下一个键锁定)Model._perform_unique_checks
和make
它使用select_for_update
(由于下一个键锁定而与MySQL一起工作)这些解决方案都没有吸引力,我也使用的PostgreSQL与MySQL在这方面不同。
答案 0 :(得分:8)
标准方法是不处理这个问题,如:
如果出于某种原因,你必须确定问题不会发生,那么你就是靠自己。
我还没有详细分析事件的顺序,但我认为使用SERIALIZABLE隔离级别确实没有帮助,它只会导致IntegrityError
(或DatabaseError
)在不同的地方长大。
覆盖Model._perform_unique_checks
听起来像是一个坏主意,如果可能的话,你最好远离猴子补丁(这里可能)。
至于使用表锁来防止不可能出现的错误......好吧,我不是一个大粉丝所以我不能推荐它。
这是一个类似问题的一个很好的答案:https://stackoverflow.com/a/3523439/176186 - 我同意,抓住IntegrityError
并重试可能是处理问题的最简单明智的方法。
答案 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
约束,会帮助。)