1. ホーム
  2. Django

Django ForeignKey逆引きクエリにおけるfilterと_setの効率比較

2022-03-02 13:49:18
<パス

前文

Django を使ってモデルを作成する場合、しばしば ForeignKey を使って 2 つのテーブルの間に多対一の外部キー関係を作成します、例えば、B には models.ForeignKey(A) A.B_set.all() または B.objects.filter(A) この2つは異なる方法です。

実際にサイトがオンラインになったとき、SEOはページの読み込み速度を重視し、増え続けるリクエストに直面したとき、この2つの方法のどちらが速いのか、という疑問も考えたことがあるのではないでしょうか?

という疑問が湧いてきたので、実行して見ます。 キュレーターはまだまだ未熟者なので、アカウントにログインして、間違っていたらコメントを残してください

実験環境

<ブロッククオート

オペレーティングシステム Manjaro Linux 17.1-rc2
Python パイソン 3.6.3
Django Django 1.11.7
データベース SQLite 3.21.0
CPU: i3-4130 @ 3.4GHz
メモリ DDR3 1600 8G + 4G

実験計画

個別の問題モデルの作成 Questions と"answer"のモデルです。 Answers 回答モデルは質問モデルと多対一の関係を持っています ForeignKey 質問と2つの回答を作成します。そして、2種類の方法でクエリーデータを1万回実行し、消費された時間を比較します。

実験的な実装

実験用モデルの作成

# myapp/models.py

from django.db import models

class Questions(models.Model):
    '''Model of the question'''
    title = models.CharField('title', max_length=100)
    content = models.TextField('description')


class Answers(models.Model):
    '''Model of answers'''
    question = models.ForeignKey(Questions, on_delete=models.CASCADE, verbose_name='question')
    content = models.TextField('answer')

それから、 django シェルに入って、モデルにデータを追加し、テストを書きます。

>>> from myapp.models import Questions, Answers

# Create the first question
Questions.objects.create(
    title = 'Is this the first question?'
    content = 'I think this is the first question, I don't know if it's true?'
    )

# Create the first answer
Answers.objects.create(
    question = Questions.objects.get(pk=1),
    content = 'You're right, this is the first question'
    )


# Create the second answer
Answers.objects.create(
    question = Questions.objects.get(pk=1),
    content = 'Question, you are the first question, but am I the second answer?'
    )

timeitを使用して、両方のメソッドで消費される時間をテストします。

from timeit import timeit

# Build a function that uses the _set method
def time_test_1():
    question = Question.objects.get(pk=1)
    answers = question.answers_set.all()


# Construct a function that uses the filter method
def time_test_2():
    question = Question.objects.get(pk=1)
    answers = Answers.objects.filter(question=question)

# Use timeit to test 10000 times
timeit(time_test_1, number=10000)
5.346277045000534

timeit(time_test_2, number=10000)
5.11136907800028

実際に何度かテストしてみたところ、少なくとも私の使い方では、このように A.B_set.all() 逆引きクエリは、常に B.objects.filter(A) フィルタリング方式では、約0.2〜0.3秒多く消費します。そのため、時間的なコストを考慮すると filter フィルタの方が効率的です。