get() 返回了多个模型——它返回了 4 个! - 但数据库中只有 1 个模型

get() returned more than one Model -- it returned 4! - but only 1 Model in db

在 Django 视图中,我使用 get_or_create 时收到 MultipleObjectsReturned 错误。 Many questions have been asked 关于这个,但那些人似乎错误地使用了这个结构。我认为我使用正确,但如果我错了请纠正我。

我有以下型号:

class MyModel(models.Model):
    field1 = models.ForeignKey('app.OtherModel', on_delete=models.SET_NULL, null=True)
    field2 = models.CharField(max_length=50, blank=True, null=True)

    field3 = models.JSONField()
    field4 = models.JSONField()

    updated = models.DateTimeField(_('Date updated'), auto_now=True)
    created = models.DateTimeField(_('Date created'), default=timezone.now)

    ...

    class Meta:
        ordering = ('-created',)
        constraints = [
            models.UniqueConstraint(fields=['field1', 'field2'], name='unique_model'),
        ]

我认为这段代码:

o, created = MyModel.objects.get_or_create(field1=value1, field2='value2',
    defaults={'field3': '...', 'field4': '...'})

所以 get 是在 UniqueConstraint 中的两个字段上执行的,其他字段在 defaults 部分中列出(如果 create是必需的)。

当 Django 执行 get_or_createget 部分时,在最后一行抛出 MultipleObjectsReturned,但仅在极少数情况下 - 大多数情况下此构造运行良好。

该视图实际上是 AWS 的 SNS 事件消息处理程序,用于 SES,处理交付延迟、退回和投诉。在 理论 中,不应恰好调用此视图两次,从而导致竞争条件。当我检查服务器日志时,我确实 没有 在抛出此错误时看到对该视图的多次调用。 (只发送了一封电子邮件,SNS 仅每 20 秒尝试重新发送一次 SNS 事件。)

This article很好地解释了问题,但不幸的是没有为我提供解决方案。

请注意,有时“-- 它返回了 2!”,有时“-- 它返回了 3!”有时“——它返回 4!”

另请注意,不是 None 的字段会引发错误:字段 1 的 ForeignKey 指向“OtherModel”中的一行,并且field2 的 CharField 包含一个字符串。

更重要的是:当我在生产中 运行 ipython 中的相同 get_or_create 时,使用与错误中提到的完全相同的值(在 Sentry 中),没有错误抛出并正确执行 get

视图中出现什么问题以及为什么会出现问题?

我正在使用 Django 3.2.2 和 Postgres 11.10。

在SQL数据库系统中,NULL不等于NULL(通常是NULL = NULLreturnsNULL),因此如果这些字段是 nullable,这意味着另一个字段可以重复任意次数。

我们可以做的是实现三个约束:两个字段的唯一性,一个字段的唯一性,如果另一个是 NULL,反之亦然,那么约束看起来像:

from django.db.models import Q, UniqueConstraint

class MyModel(models.Model):
    field1 = models.ForeignKey(
        'app.OtherModel',
        on_delete=models.SET_NULL,
        null=True
    )
    field2 = models.CharField(max_length=50, blank=True, null=True)
    # …

    class Meta:
        ordering = ('-created',)
        constraints = [
            UniqueConstraint(fields=['field1', 'field2'], name='unique_model1'),
            UniqueConstraint(fields=['field1']<strong>, condition=Q(field2=None)</strong>, name='unique_model2'),
            UniqueConstraint(fields=['field2']<strong>, condition=Q(field1=None)</strong>, name='unique_mode3'),
        ]

然而,并非所有数据库后端都可能强制执行带有条件的唯一约束。