8

Djangoアプリのデータベースクエリを最適化しようとしています。簡単な例を次に示します。

class Label(models.Model):
    name = models.CharField(max_length=200)
    # ... many other fields ...

class Thing(models.Model):
    name = models.CharField(max_length=200)
    labels = models.ManyToManyField(Label)

すべてLabelのsとThingsをフェッチし、それらをJSONデータ構造に配置する関数があります。ここで、sはs(主キー)を使用してsをThing参照します。このようなもの:Labelid

{
    'labels': [
        { 'id': 123, 'name': 'label foo' },
        ...
    ],
    'things': [
        { 'id': 45, 'name': 'thing bar', 'labels': [ 123, ... ] },
        ...
    ]
}

Djangoを使用してそのようなデータ構造を取得する最も効率的な方法は何ですか?LsとTsがあり 平均がxsであるとします。Label ThingThing Label

方法1:

data = {}
data['labels'] = [model_to_dict(label) for label in Label.objects.all()]
data['things'] = [model_to_dict(thing) for thing in Thing.objects.all()]

これにより、(1 + 1 + T)データベースクエリが作成されます。これは、それぞれのsを個別model_to_dict(thing)にフェッチする必要があるためです。LabelThing

方法2:

data = {}
data['labels'] = [model_to_dict(label) for label in Label.objects.all()]
data['things'] = [model_to_dict(thing) for thing in
                    Thing.objects.prefetch_related('labels').all()]

これにより、(1 + 1 + 1)データベースクエリのみが作成されます。Thingこれは、フェッチされたLabelsが単一の追加クエリでプリフェッチされるようになったためです。

これはまだ満足のいくものではありません。 prefetch_related('labels')同じの多くのコピーをフェッチしLabelますが、必要なのはそれらのコピーだけですididLabelsのみをプリフェッチする方法はありますか?試しprefetch_related('labels__id')ましたが、うまくいきませんでした。また、 Tが大きい(数百)ため、大きな句prefetch_related('labels')を含むSQLクエリが生成されることも懸念されます。Lははるかに小さい(<10)ので、代わりにこれを行うことができます。IN

方法3:

data = {}
data['labels'] = [model_to_dict(label) for label in
                    Label.objects.prefetch_related('thing_set').all()]
things = list(Thing.objects.all())
# plug in label ids by hand, and also fetch things that have zero labels
# somehow

これにより、句が小さくなりますが、 aに複数のsがある場合、重複するsをフェッチするINため、それでも満足のいくものではありません。prefetch_related('thing_set')ThingThingLabel

概要:

LabelThingで接続されていManyToManyFieldます。とにかくすべて Labelのsとsをフェッチしています。Thingでは、どうすれば多対多の関係を効率的に取得できますか?

4

1 に答える 1

9

わかった。through質問へのコメントが私にテーブルを指摘してくれたilvarに感謝します。

明示的なスルーモデルを指定しない場合でも、関連付けを保持するために作成されたテーブルに直接アクセスするために使用できる暗黙的なスルーモデルクラスがあります。モデルをリンクするための3つのフィールドがあります。

短編小説:

# Fetch all labels and things:
labels = list(Label.objects.all())
things = list(Thing.objects.all())
# Fetch all label-thing pairs:
labels_of = defaultdict(lambda: [])
for pair in Thing.labels.through.objects.filter(label__in=labels):
    labels_of[pair.thing_id].append(pair.label_id)
# Put everything together:
data = {}
data['labels'] = [model_to_dict(label) for label in labels]
data['things'] = []
for thing in things:
    thing_dict = model_to_dict(thing, exclude='labels')
    thing_dict['labels'] = labels_of[thing.id]
    data['things'].append(thing_dict)

これにより、(1 + 1 + 1)クエリが作成され、何も繰り返しフェッチされません。最初のforループを次のように変更することもできます。

for pair in Thing.labels.through.objects.filter(thing__in=things):

Labelsよりもsが多い場合は、句Thingが小さいクエリになります。IN

Django-debug-toolbardebugsqlshell管理コマンドは、コードの一部が行っているクエリを実際に確認するのに最適です。

于 2012-04-24T02:09:13.473 に答える