LoginSignup
9
10

More than 5 years have passed since last update.

Djangoで生成されたクエリを確認したら、大量に発行されていた

Posted at

Django:1.10

Djangoでクエリを生成する時、よく考えて書かないと大量にクエリが発行されてしまいます。

クエリのログ出力

まずはクエリをサーバのコンソールに出力するため、manage.pyにログの設定を書く。

manage.py
LOGGING = {
    'version': 1,
    'formatters': {
        'all': {
            'format': '%(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'all',
        },
    },
    'loggers': {
        'django': {
           'handlers': ['console'],
           'level': 'DEBUG',
        },
    },
}

クエリの確認

前回使った、選択した著者の出版した本一覧を取得するクエリを実行してみます。

DjangoでIN演算子を使ったクエリを生成する方法
http://qiita.com/nakkun/items/86a94e65fe6785325f54

ソース
books = Book.objects.filter(author_id__in = form.cleaned_data['author'])
実行結果
(0.002) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)

想像通りのSQLが実行されていました。
以下のように、「author__id__in」を使っても同じクエリになりました。
(「_」の数が違う)

ソース
books = Book.objects.filter(author__id__in = form.cleaned_data['author'])

「author__id__in」の場合は、joinしてくれるのかなと思っていましたが、そうなっていませんでした。

クエリ大量発生!?

ここまでは特に問題ないのですが、その後に著者名を表示しようとした場合、以下のような実行結果になってしまいます。

ソース
books = Book.objects.filter(author_id__in = form.cleaned_data['author'])
[print(book.author.name) for book in books]
実行結果
(0.003) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 1; args=(1,)
森 博嗣
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光

かなり無駄なクエリを発行してしまっています。

なので、次のようなjoinしたクエリを発行してくれるソースを書く必要があります。

ソース
books = Book.objects.select_related().filter(author_id__in = form.cleaned_data['author'])
[print(book.author.name) for book in books]
実行結果
(0.004) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id", "books_author"."id", "books_author"."name" FROM "books_book" INNER JOIN "books_author" ON ("books_book"."author_id" = "books_author"."id") WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光

これでSQLの発行が1回で済みます。

知らず知らずのうちに大量のSQLを投げてしまわないように、慣れないうちはSQLを確認しながら開発した方がいいかもしれません。

補足

他の方法を3つほど。

① prefetch_relatedを使う方法

ソース
books = Book.objects.filter(author_id__in = form.cleaned_data['author']).prefetch_related('author')
[print(book.author.name) for book in books]
実行結果
(0.001) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光

② Authorモデルを起点にする

ソース
authors = Author.objects.filter(id__in = form.cleaned_data['author'])
[[print(book.atuhor.name) for book in author.book_set.all()] for author in authors]
実行結果
(0.002) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" = 1; args=(1,)
森 博嗣
(0.000) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" = 3; args=(3,)
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光

③ Authorモデルを起点にprefetch_relatedを使う

ソース
authors = Author.objects.filter(id__in = form.cleaned_data['author']).prefetch_related("book_set")
[[print(book.atuhor.name) for book in author.book_set.all()] for author in authors]
実行結果
(0.002) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
(0.002) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10