21

JSON APIエンドポイントからDjangoデータベースにデータをコピーするための小さなdjangoコマンドを書いています。で実際にオブジェクトを作成した時点でobj, created = model.objects.get_or_create(**filters)MultipleObjectsReturnedエラーが発生します。get_or_create私の理解では、既に存在するオブジェクトを作成しようとすると、代わりにそれを「取得」するだけなので、これは私にとって驚くべきことです。

複製しているデータベースの整合性については確信が持てませんが、同じオブジェクトが複数ある場合でも、それらをローカルの Django データベースにロードするときに、get_or_create でそれ以上のものを取得しないようにするべきではありません。 1コピー?

誰でもこれを説明できますか?読者を動揺させたくなかっただけで、より詳細な情報を提供できることをうれしく思います。

4

4 に答える 4

27

サンプルコード

次のモデルがあるとします。

class DictionaryEntry(models.Model):
    name = models.CharField(max_length=255, null=False, blank=False)
    definition = models.TextField(null=True, blank=False)

および次のコード:

obj, created = DictionaryEntry.objects.get_or_create(
    name='apple', definition='some kind of fruit')

get_or_create

のコードをget_or_create見たことがない場合:

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

ウェブサーバーについて...

2ここで、両方のワーカー プロセスが同時にデータベースにアクセスできるWeb サーバーがあるとします。

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False # <===== nope not there...
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

タイミングが正しければ (言い方によっては間違っても)、両方のプロセスで検索が行われ、アイテムが見つからない可能性があります。両方がアイテムを作成する場合があります。すべて順調...

MultipleObjectsReturned: get() returned more than one KeyValue -- it returned 2!

すべてが順調です... 3 回目に電話get_or_createするまでは、「3 回目は魅力的です」と彼らは言います。

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False # <==== kaboom, 2 objects.
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

unique_together

どうすればこれを解決できますか?おそらく、データベース レベルで制約を適用します。

class DictionaryEntry(models.Model):
    name = models.CharField(max_length=255, null=False, blank=False)
    definition = models.TextField(null=True, blank=False)
    class Meta:
        unique_together = (('name', 'definition'),)

関数に戻ります。

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True # <==== this handles IntegrityError
     return instance, created

あなたが以前と同じ人種を持っていて、どちらもアイテムを見つけられず、挿入に進んだとします。そうすることで、彼らはトランザクションを開始し、そのうちの 1 人がレースに勝ち、もう 1 人はIntegrityError.

mysql ?

この例では、 (私の場合)に変換される aTextFieldを使用しています。制約を追加すると、 .mysqlLONGTEXTunique_togethersyncdb

django.db.utils.InternalError: (1170, u"BLOB/TEXT column 'definition' used in key specification without a key length")

MultipleObjectsReturnedそのため、手動で処理する必要がある場合があります。

可能な解決策

  • を に置き換えることができる場合がありTextFieldますCharField
  • で計算して使用できるCharFieldの強力なハッシュであるを追加することも可能です。TextFieldpre_saveunique_together
于 2015-04-08T17:12:03.657 に答える
9

名前が示すように、get_or_create model.objects.get()s またはmodel.objects.create()s。

概念的には次のものと同等です。

try:
   model.objects.get(pk=1)
except model.DoesNotExist:
   model.objects.create(pk=1)

ソースは、これらのタイプの質問に対する決定的な答えを見つける場所です。ヒント: 検索def get_or_create. ご覧のとおり、この関数DoesNotExistは try/except でのみキャッチします。

def get_or_create(self, **kwargs):
    """
    Looks up an object with the given kwargs, creating one if necessary.
    Returns a tuple of (object, created), where created is a boolean
    specifying whether an object was created.
    """
    assert kwargs, \
            'get_or_create() must be passed at least one keyword argument'
    defaults = kwargs.pop('defaults', {})
    lookup = kwargs.copy()
    for f in self.model._meta.fields:
        if f.attname in lookup:
            lookup[f.name] = lookup.pop(f.attname)
    try:
        self._for_write = True
        return self.get(**lookup), False
    except self.model.DoesNotExist:
于 2013-07-31T04:14:38.697 に答える
4

get_or_create() API で MultipleObjectsReturned エラーが発生する可能性がある別の状況は、同じクエリ パラメータ セットで同時にこの API を呼び出す複数のスレッドがある場合です。

Python で一意の行を作成するために、try... catch... だけに頼ってもうまくいきません。この API を使用しようとしている場合は、データベース内の適切な列に一致する一意性制約を設定する必要があると思います。

参照: https://code.djangoproject.com/ticket/12579

于 2014-02-06T11:00:31.963 に答える