Django のデータベース層は、開発者がデータベースから多くを得ることを助ける色々 な方法を提供しています。このドキュメントは関連するドキュメントのリンクを集めて 色々なヒントを加え、データベース利用の最適化を試みる時に踏むべきステップを概略 する多くの見出しのもとに整理しています。
一般的なプログラミングの実践として、これは言うまでもないことです。 実行されるクエリと実行にかかるコスト を見てく ださい。また、 django-debug-toolbar のような外部のプロジェクトや、データベー スを直接モニタするツールを使っても良いでしょう。
必要に従って、速度を最適化するのか、メモリを最適化するのか、または両方なのかを 思い出してください。時には一方の最適化が他方にとって有害なものになりますが、ま た時には互いに助けになります。また、データベースプロセスでの処理は、 Python プ ロセスで行われる同じ量の処理と同じコストにならないかもしれません。優先度がどう か、バランスを取るべきか、全ての計測が必要か、を決定するのはあなたです。これら はアプリケーションとサーバに依存するでしょうから。
以下の全てにおいて、変更が利益を生んでいることを確かめるため、そして大きすぎる 利益がコードの可読性を下げていないかを確かめるため、変更を行うごとに計測するの を忘れないようにしてください。以下の示唆 ** 全て ** について言えることですが、 あなたの環境では一般的な原則が当てはまらないかもしれない、もしかすると逆かもし れないということに注意してください。
以下が含まれます:
django.db.models.Field.db_index
を使います。上記の明らかなことは既に行ってあると仮定します。このドキュメントの残りでは、必 要のないことをしないためにどのように Django を使うかに焦点を当てます。このド キュメントはまた、 汎用的なキャッシング のような高くつ く操作全般に適用するような最適化技術は扱いません。
クエリセット を理解することはシンプルなコードで 良いパフォーマンスを得るために欠かせません。とりわけ:
パフォーマンスの問題を避けるには、次のことを理解することが大切です:
QuerySet
全体をキャッシュするのと同様、 ORM オブジェクトの属性の結果も
キャッシュされます。一般的に呼び出し可能でない属性はキャッシュされます。例えば
ブログでのモデル例 を前提にすると:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog オブジェクトはこの時点で検索されます
>>> entry.blog # キャッシュバージョンが使われ、 DB アクセスしません
しかし一般的に、呼び出し可能な属性は毎回 DB 参照を行います:
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # クエリが実行された
>>> entry.authors.all() # クエリがまた実行された
テンプレートコードを読む時には注意してください。テンプレートシステムはカッコを 使うことを許容しませんが、上記の区別なく呼び出し可能オブジェクトを自動的に呼び ます。
自分のカスタムプロパティに注意してください。キャッシュを実装するかは開発者に任 されています。
iterator()
を使う¶多くのオブジェクトを持っている時、 QuerySet
のキャッシング動作は大量のメモ
リ使用につながる可能性があります。この場合、
iterator()
が助けになるかもしれませ
ん。
具体例として:
必要なSQLを生成するのにこれらで不十分なら:
QuerySet.extra()
を使う¶移植性は比較的低いですが、より強力なメソッドが
extra()
です。これはクエリに対する明
示的な SQL の追加を可能にします。これでもまだ十分強力じゃないなら:
データを検索したりモデルを作成するためのカスタム SQL
を自分で書いてください。 django.db.connection.queries
を使うと、 Django が
書き出したクエリを見ることができますので、そこから始めましょう。
一般的に、一つのデータ「集合」の別々の部分のために、データベースを複数回叩くの は、一つのクエリで一度に検索するよりも効率が低いです。 もしクエリがループの中で実行されていて、それ故に多くのデータベースクエリを一つ だけにまとめることが必要なら、特に重要です。
QuerySet.values()
と values_list()
を使う¶値の dict
か list
が必要なだけで、 ORM モデルオブジェクトが必要ないな
ら、 values()
を適切に使ってください。
これらはテンプレートコードのモデルオブジェクトを置き換えるのに便利です。辞書が
テンプレートで使われているものと同じ属性を持っているなら、うまく行きます。
QuerySet.defer()
と only()
を使う¶必要のない (またはほとんどの場合に必要ない) データベースカラムがあると分かって
いるなら、それらをロードしないように
defer()
と
only()
を使ってください。
もしそのようなカラムを 使う 場合は、 ORM が個別のクエリで取りに行かなければ
ならないことに注意してください。不適切にそれを使うなら、悲観的に考えましょう。
また、遅延フィールドを使ってモデルを構築する時に Django の中でいくらかの (小さ
な追加の) オーバーヘッドが発生することに注意してください。計測なしでの遅延
フィールドの使用に積極的になりすぎないでください。わずかなカラムしか使っていな
い時でも、データベースは結果の 1 つの行のためにほとんどの非 text 型、非
VARCHAR 型のデータをディスクから読み出さなければなりません。 defer()
と
only()
メソッドは多くのテキストデータや Python データに戻すために多くの処
理が必要となるフィールドのロードを避けるのにはとても便利です。例によって、まず
は計測、それから最適化をしましょう。
数を数えたいだけなら、 len(queryset)
を使うよりもこちらの方が良いです。
count()
と exists()
を使いすぎない¶QuerySet からの他のデータが必要なら、それを評価しましょう。
例えば、 body
属性とユーザへの多対多のリレーションを持っている電子メールモ
デルを考えましょう。以下のテンプレートコードは最適です:
{% if display_inbox %}
{% with emails=user.emails.all %}
{% if emails %}
<p>You have {{ emails|length }} email(s)</p>
{% for email in emails %}
<p>{{ email.body }}</p>
{% endfor %}
{% else %}
<p>No messages today.</p>
{% endif %}
{% endwith %}
{% endif %}
これが最適な理由は:
with
を使うと、あとで使われるために user.email_all
を変数に保存
します。再利用できるキャッシュが使われます。{% if emails %}
行は QuerySet.__nonzero__()
の呼び出しを引き起こし
ます。これは user.emails.all()
がデータベースで実行されることにつながり
ます。少なくとも一行目が ORM オブジェクトに変換されます。結果がなければ
False を返し、あれば True を返します。{{ emails|length }}
は QuerySet.__len__()
を呼びます。他のクエリを
実行せずにキャッシュの残りを満たします。for
ループは既に満たされているキャッシュの上に繰り返されます。全体として、このコードは 1 個か 0 個のデータベースクエリを実行します。
唯一の計画的な最適化の実施が with
タグ の使用です。どの箇所で
QuerySet.exists()
や QuerySet.count()
を使っても追加のクエリの原因にな
ります。
QuerySet.update()
と delete()
を使う¶オブジェクトを検索してロードし、何かの値をセットして個別に保存するよりも、 QuerySet.update() 経由でバルクの SQL UPDATE 文を使うほうが良いです。同 様に、可能な限り バルクでの削除 を使いましょ う。
しかしながら、これらのバルクアップデートメソッドは個別のインスタンスの
save()
や delete()
メソッドを呼べないことに注意してください。つまり、
通常のデータベースオブジェクト シグナル によって起動され
るものも含め、開発者がこれらのメソッドに追加したいかなるカスタムの動作も実行さ
れないことになります。
外部キーの値が必要なだけなら、関連するオブジェクトの全体を取得してそのプライマ リキーを得るのではなく、既に取得しているオブジェクトの外部キーの値を使いましょ う。例えばこのようにする代わりに:
entry.blog.id
こうします:
entry.blog_id
複数のオブジェクトを作る時、可能であれば、
bulk_create()
メソッドを使って SQL ク
エリの数を減らしましょう。例えば:
Entry.objects.bulk_create([
Entry(headline="Python 3.0 Released"),
Entry(headline="Python 3.1 Planned")
])
が以下よりも望ましいです:
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
このメソッドに関する注意
がたくさんありますので、ユースケースに合致しているか確認してください。
このことは ManyToManyFields
にも当
てはまります。このようにすることは:
my_band.members.add(me, my_friend)
以下よりも良いです:
my_band.members.add(me)
my_band.members.add(my_friend)
ここで Bands
と Artists
は多対多の関係を持っているとします。
Oct 26, 2017