8
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Django】クエリ数を減らすための工夫たち(随時追加予定)

Last updated at Posted at 2018-12-13

この記事で書いていること

  • ORMを使っていると、知らぬ間に大量のSQLが発行されてる恐れがある
  • クエリ数を発行しないための工夫がいくつかあるので、それを紹介する
  • 新しい使えるテクニックを見つけたら、随時加筆予定

ORMでは知らぬうちに大量のクエリが発行されているかもしれない

私が触ったことのあるフレームワークはRails、Djangoだけなので、ORマッパーがあるのが当たり前という環境で育ってきました。

そのため、DBにアクセスするためにSQLが発行されていることをあまり意識してきませんでした。(お恥ずかしい限りですが)

これから職業プログラマーとして成長していくためにはそんなんじゃあかんという訳で、絶賛試行錯誤中です。

今回は私が調べたり、先輩から教えてもらったクエリ数を抑える工夫やテクニックを紹介していこうと思います。

クエリ数を観測するには、「django-debug-toolbar」を使う

まず、クエリ数を改善するためには、何のSQLがどれくらい走っているのか計測できる必要があります。

そこで取り入れるのが、「django-debug-toolbar」というパッケージです。

こんな感じのサイドバーが画面に表示されて、直前の処理でどれくらいSQLが走ったのか、見れるようになります。
スクリーンショット 2018-12-13 15.36.07.png

インストール

公式DOCSにインストールの手順が書かれています。順にコピペしていくだけでオッケーです。
https://django-debug-toolbar.readthedocs.io/en/stable/installation.html

クエリ数を減らすのに役立つテクニック集

では、ここから実際にクエリ数を減らすテクニックを書いていきます。

ループの中でクエリが走る処理を書かない

テクニックというか、心がけ。

ループの中でUser.objects.filter(name="hoge")とか書いてるコードに違和感を持てるようになろう。

select_related()

公式DOCSのここに詳細が書いてあります。
https://docs.djangoproject.com/ja/2.1/ref/models/querysets/#select-related

公式の例をそのまま引っ張ってくると、

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog

Entryクラスのリレーション先である、Blogクラスのデータもselect_related()が呼ばれたタイミングでDBから引っ張ってきているので、最後の行でe.blogを実行した時にクエリが走りません。

的なことが書いてあります。

もし、select_related()を実行せず、get()だけを使ってEntryクラスのインスタンスをeに格納した場合、最後のe.blogを実行したタイミングでもう一度クエリが走ってしまうことになります。

上記のような簡単な例では、クエリ数にさほど差は出てきませんが、取得したインスタンスのリレーション先のインスタンスをループ処理の中などで取り出す処理を書いていたりしたら、select_relatedの有無による差は絶大になります。

ちなみに、リレーション先が複数ある場合、

e = Entry.objects.select_related('blog', 'something').get(id=5)

みたいに、コンマつなぎで一気に書けます。

bulk_create()

公式DOCSはこちら
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#bulk-create

何かデータをcreateする時は、当然ながらクエリが走ります。

1件ずつ逐一createしていると、登録件数が増えるほど、クエリ数が増えてしまいます。

ある程度まとまった量のデータをまるっと処理することも当然よくあるので、それでは困ってしまいます。

Djangoはbulk_createというやつを用意してくれています。

以下、公式記載の例

>>> Entry.objects.bulk_create([
...     Entry(headline='This is a test'),
...     Entry(headline='This is only a test'),
... ])

このように、インスタンスを要素にした配列を引数にとって、1クエリで全てのレコードを作成することができます。

バッチ処理とも相性が良く、公式にはその例も載っているので、一目見ておくと良いと思います。

bulk_update()

複数のレコードを更新したい時に便利なパッケージ。

何かのレコードを変更し、save()で保存する時、UPDATEというクエリが走ります。

変更レコードが1件増えるごとに1クエリ増えていってしまいます。

そこで、bulk_updateを使うと、変更したいインスタンスを配列に格納しておけば、1つのクエリで全て更新できるようになります。

使用イメージとしては、こんな感じ。

from django_bulk_update.helper import bulk_update

updates = []

for instance in instances:
    instance.hoge = "fuga"
    updates.append(instance)

bulk_update(updates, update_fields=['hoge'])

インストール

Djangoのbuilt-inではなく、パッケージをインストールして使います。

詳細は公式のGithubを見てください。
https://github.com/aykut/django-bulk-update

こちらサイトも参考にさせてもらいました。調べてると良くお見かけするサイトさんです。いつもお世話になっております。
https://tokibito.hatenablog.com/entry/20160128/1453914673

8
13
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
8
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?