web-dev-qa-db-ja.com

django select_related-いつ使用するか

DjangoでORMクエリを最適化しようとしています。 connection.queriesを使用して、Djangoが生成するクエリを表示します。

私はこれらのモデルを持っていると仮定します:

class Book(models.Model):
 name= models.CharField(max_length=50)
 author = models.ForeignKey(Author)

class Author(models.Model):
 name = models.CharField(max_length=50)

たとえば、特定のWebページを生成するとき、すべての書籍を表示し、各書籍の隣に著者名を表示するとします。また、すべての著者を別々に表示します。

だから私は使用する必要があります

Book.objects.all().select_related("author")

その結果、JOINクエリが発生します。前に行を行っても:

Author.objects.all()

テンプレートには明らかに{{book.author.name}}
したがって、質問は、外部キー値(著者)にアクセスするときに、Django=が別のクエリからそのオブジェクトをすでに持っている場合、追加のクエリ(もしそうなら、その場合、select_relatedを使用すると実際にパフォーマンスのオーバーヘッドが発生しますか?

23
user3599803

あなたは実際に2つの異なる質問をしています:

1。 select_relatedを使用すると実際にパフォーマンスのオーバーヘッドが発生しますか?

Django Query Cache に関するドキュメントが表示されます。

QuerySetの評価を理解する

パフォーマンスの問題を回避するには、以下を理解することが重要です。

  • querySetsが遅延していること。

  • それらが評価されるとき。

  • データがメモリに保持される方法。

要約すると、Django=同じQuerySetオブジェクト内で評価されたメモリ内キャッシュ結果、つまり、次のようなことをした場合:

books = Book.objects.all().select_related("author")
for book in books:
    print(book.author.name)  # Evaluates the query set, caches in memory results
first_book = books[1]  # Does not hit db
print(first_book.author.name)  # Does not hit db  

Select_relatedでAuthorsをプリフェッチしたときにdbを1回だけヒットすると、これらすべてがINNER JOINの単一のデータベースクエリになります。

しかし、これはクエリセット間でキャッシュを行わず、同じクエリでもキャッシュしません:

books = Book.objects.all().select_related("author")
books2 = Book.objects.all().select_related("author")
first_book = books[1]  # Does hit db
first_book = books2[1]  # Does hit db

これは実際に docs で指摘されています:

上記の明らかなことを行ったと仮定します。このドキュメントの残りの部分では、不必要な作業を行わないようにDjangoを使用する方法に焦点を当てています。このドキュメントは、 汎用キャッシング

2。 if Djangoはすでに別のクエリからそのオブジェクトを持っていますが、それでも追加のクエリ(各本)になりますか?

Django=がORMクエリキャッシングである場合、これは意味があります。これは非常に異なる問題です。ORMクエリキャッシング、つまり、クエリbeforeを実行してから、同じクエリlater、データベースが変更されていない場合、結果は高価なデータベース検索からではなく、キャッシュから取得されます。

答えはDjangoではなく、公式にサポートされているわけではありませんが、はい、非公式に、はい、サードパーティのアプリを使用しています。このタイプのキャッシングを可能にする最も関連性のあるサードパーティアプリは次のとおりです。

  1. Johnny-Cache (古い、Djangoをサポートしていない> 1.6)
  2. Django-Cachalot (新しい、1.6、1.7をサポートし、dev 1.8でもまだ)
  3. Django-Cacheops (新しい、サポートPython 2.7または3.3 +、Django 1.8+およびRedis 2.6+(4.0+推奨)) )

クエリのキャッシュを探し、最初にプロファイルを作成し、ボトルネックを見つけ、それらが問題を引き起こしている場合は最適化することを覚えているなら、それらを見てください。

本当の問題は、プログラマーが間違った場所で間違った時間に効率を心配しすぎているということです。早すぎる最適化は、プログラミングにおけるすべての悪(または少なくともその大部分)の根源です。ドナルド・クヌース。

20
danius

Djangoは他のクエリを知りません! Author.objects.all()Book.objects.all()はまったく異なるクエリセットです。したがって、ビューの両方をテンプレートコンテキストに渡し、テンプレートで次のようなことを行う場合:

 {book in books %%} 
 {{book.author.name}} 
 {%endfor%} 

そして、[〜#〜] n [〜#〜]本があると、これは[〜#〜]になりますn [〜#〜]余分なデータベースクエリ(すべての書籍と著者を取得するためのクエリ以外)

代わりにBook.objects.all().select_related("author")を実行した場合、上記のテンプレートスニペットでは追加のクエリは実行されません。

現在、select_related()はもちろん、クエリにオーバーヘッドを追加します。 Book.objects.all() Djangoを実行すると、_SELECT * FROM BOOKS_の結果が返されます。代わりにBook.objects.all().select_related("author") Djangoを実行すると、_SELECT * FROM BOOKS B LEFT JOIN AUTHORS A ON B.AUTHOR_ID = A.ID_の結果が返されます。したがって、各本について、本の列とそれに対応する著者の両方を返します。ただし、このオーバーヘッドは、データベースにヒットするオーバーヘッド[〜#〜] n [〜#〜]回(説明のとおり)前)。

そのため、_select_related_は小さなパフォーマンスオーバーヘッドを作成しますが(各クエリはデータベースからより多くのフィールドを返します)、実際に必要な場合を除いて使用することは有益ですonly =クエリしている特定のモデルの列。

最後に、データベースで実際に実行されているクエリの数(および正確に)を実際に確認するには、Django-debug-tooblar( https://github.com/Django-debug-toolbar/Django -debug-toolbar )。

17
Serafeim
_Book.objects.select_related("author")
_

十分です。 Author.objects.all()は不要です

_{{ book.author.name }}
_

_book.author_はすでに事前に入力されているため、データベースにヒットしません。

1
doniyor