データベース管理は、バックエンド開発の最も重要な側面の 1 つです。データベースを適切に最適化すると、応答時間が短縮され、ユーザー エクスペリエンスが向上します。 この記事では、Django アプリケーションの速度のためにデータベースを最適化する方法について説明します。ただし、各概念を個別に深く掘り下げることはしません。したがって、詳細については、 を参照してください。 Django の公式ドキュメント Django でのクエリを理解する Django のクエリセットを理解することが最適化の鍵となるため、次の点に注意してください。 クエリセットは遅延型です。つまり、クエリセットに対して反復処理などの特定のアクションを実行するまで、対応するデータベース リクエストは行われません。 返される値の数を指定して、データベース クエリの結果を常に制限します。 Django では、反復、スライス、キャッシング、および 、 などの python メソッドによってクエリセットを評価できます。それらを最大限に活用するようにしてください。 len() count() Django クエリセットはキャッシュされるため、同じクエリセットを再利用しても複数のデータベース リクエストが行われないため、データベース アクセスが最小限に抑えられます。 必要なものはすべて一度に取得しますが、必要なものだけを取得していることを確認してください。 Django でのクエリの最適化 データベースの索引付け データベースのインデックス作成は、データベースからレコードを取得する際のクエリを高速化するための手法です。アプリケーションのサイズが大きくなると、速度が低下する可能性があります。ユーザーは、必要なデータを取得するのにかなり長い時間がかかることに気付くでしょう。したがって、膨大な量のデータを生成する大規模なデータベースを操作する場合、インデックス作成は交渉の余地のない操作です。 索引付けは、さまざまなフィールドに基づいて多数のデータをソートする方法です。データベース内のフィールドにインデックスを作成すると、フィールド値と、それが関連付けられているレコードへのポインターを含む別のデータ構造が作成されます。次に、このインデックス構造がソートされ、バイナリ検索が可能になります。 たとえば、Sale という名前の Django モデルを次に示します。 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, ) charged_amount = models.PositiveIntegerField() 次のように Django モデルを定義する際に、特定のフィールドにデータベース インデックスを追加できます。 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() このモデルの移行を実行すると、Django はテーブル Sales にデータベース インデックスを作成し、インデックスが完了するまでロックされます。少量のデータと非常に少数の接続を使用するローカル開発セットアップでは、この移行は瞬時に感じるかもしれませんが、実稼働環境について話すと、ロックを取得して作成するときにダウンタイムを引き起こす可能性のある多数の同時接続を持つ大規模なデータセットがあります。データベースのインデックスには時間がかかる場合があります。 以下に示すように、2 つのフィールドに対して 1 つのインデックスを作成することもできます。 # models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() class Meta: indexes = [ ["sold_at", "charged_amount"]] データベースキャッシング データベース キャッシングは、データベースから迅速な応答を得るための最良の方法の 1 つです。これにより、データベースへの呼び出しが少なくなり、過負荷が防止されます。標準のキャッシュ操作は、次の構造に従います。 Django は、Memcached や Redis などのさまざまなキャッシュ バックエンドを使用できるキャッシュ メカニズムを提供し、同じクエリを複数回実行することを回避できます。 Memcached は、キャッシュされた結果を 1 ミリ秒未満で提供することを保証するオープンソースのインメモリ システムです。セットアップとスケーリングは簡単です。一方、Redis は、Memcached と同様の特性を持つオープンソースのキャッシュ ソリューションです。ほとんどのオフライン アプリは、以前にキャッシュされたデータを使用します。これは、クエリの大部分がデータベースに到達しないことを意味します。 ユーザー セッションは Django アプリケーションのキャッシュに保存する必要があります。Redis はデータをディスク上に保持するため、ログインしているユーザーのすべてのセッションは、データベースではなくキャッシュから発生します。 Django で Memcache を使用するには、以下を定義する必要があります。 使用するキャッシュ バックエンドを定義します。 BACKEND: の値。ip は Memcached デーモンの IP アドレス、 は Memcached が実行されているポート、または適切なスキームを使用した Redis インスタンスを指す URL です。 LOCATION: ip ip:port port Memcached でデータベース キャッシングを有効にするには、次のコマンドを使用して pip を使用して をインストールします。 pymemcache pip install pymemcache 次に、次のように でキャッシュ設定を構成できます。 settings.py CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } } 上記の例では、Memcached は localhost (127.0.0.1) ポート 11211 で実行され、 バインディングを使用しています。 pymemcache 同様に、Redis を使用してデータベース キャッシングを有効にするには、次のコマンドを使用して pip を使用して Redis をインストールします。 pip install redis 次に、次のコードを追加して、 でキャッシュ設定を構成します。 settings.py CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379', } } Memcached と Redis を使用して、ユーザー認証トークンを保存することもできます。ログインするすべての人がトークンを提供する必要があるため、これらのすべての手順により、データベースのオーバーヘッドが大幅に増加する可能性があります。キャッシュされたトークンを使用すると、データベースへのアクセスが大幅に高速化されます。 可能であれば反復子を使用する 通常、Django のクエリセットは、評価が行われるとその結果をキャッシュし、そのクエリセットをさらに操作する場合は、最初にキャッシュされた結果があるかどうかを確認します。ただし、 を使用すると、キャッシュがチェックされず、結果がデータベースから直接読み取られ、結果がクエリセットに保存されません。 iterator() さて、これがどのように役立つか疑問に思っているに違いありません。大量のメモリをキャッシュする多数のオブジェクトを返すが、一度しか使用する必要がないクエリセットを考えてみましょう。このような場合は、 を使用する必要があります。 iterator() たとえば、次のコードでは、すべてのレコードがデータベースからフェッチされ、メモリにロードされ、それぞれを反復処理します。 queryset = Product.objects.all() for each in queryset: do_something(each) 一方、 を使用すると、Django は SQL 接続を開いたままにして各レコードを読み取り、次のレコードを読み取る前に を呼び出します。 iterator() do_something() queryset = Product.objects.all().iterator() for each in queryset: do_something(each) 持続性データベース接続の使用 Django はリクエストごとに新しいデータベース接続を作成し、リクエストが完了すると閉じます。この動作は、デフォルト値が 0 の によって引き起こされます。これは、サイトのトラフィック量によって決まります。音量が大きいほど、接続を維持するのに必要な秒数が長くなります。通常は、60 などの低い数値から始めることをお勧めします。 CONN_MAX_AGE 追加のオプションを でラップする必要があります。 : OPTIONS ドキュメンテーション DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dashboard', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'CONN_MAX_AGE': '60', } } } クエリ式の使用 クエリ式は、更新、作成、フィルター、並べ替え、注釈、または集計操作で使用できる値または計算を定義します。 Django でよく使用される組み込みクエリ式は F 式です。それがどのように機能し、役立つか見てみましょう。 これらの式は および で定義されていますが、便宜上、それらは利用可能であり、通常は からインポートされます。 注: django.db.models.expressions django.db.models.aggregates django.db.models F式 Django クエリセット API では、 式を使用してモデル フィールド値を直接参照します。これにより、モデル フィールド値を参照し、データベースから Python メモリにフェッチすることなく、それらに対してデータベース アクションを実行できます。代わりに、Django は オブジェクトを使用して、必要なデータベース アクティビティを定義する SQL 句を生成します。 F() F() たとえば、すべての製品の価格を 20% 引き上げたい場合、コードは次のようになります。 products = Product.objects.all() for product in products: product.price *= 1.2 product.save() ただし、 を使用すると、次のように単一のクエリでこれを実行できます。 F() from django.db.models import F Product.objects.update(price=F('price') * 1.2) と の使用 select_related() prefetch_related() Django は、データベース要求の数を最小限に抑えてクエリセットを最適化するための および 引数を提供します。 select_related() prefetch_related() 公式のDjangoドキュメントによると: は、外部キー関係を「たどり」、クエリを実行するときに追加の関連オブジェクト データを選択します。 select_related() は、関係ごとに個別のルックアップを行い、Python で「結合」を行います。 prefetch_related() select_related() 選択するアイテムが単一のオブジェクトである場合、 を使用します。これは、前方の 、 、および後方の フィールドを意味します。 select_related() ForeignKey OneToOne OneToOne を使用して、1 対多および 1 対 1 接続の単一インスタンスのすべての関連オブジェクトを返す単一クエリを作成できます。クエリが実行されると、 は外部キー リレーションシップから余分な関連オブジェクト データを取得します。 select_related() select_related() は、SQL 結合を生成することによって機能し、関連するオブジェクトの列を 式に含めます。その結果、 は、同じデータベース クエリで関連アイテムを返します。 select_related() SELECT select_related() はより高度なクエリを生成しますが、取得したデータはキャッシュされるため、取得したデータの処理に追加のデータベース リクエストは必要ありません。 select_related() 構文は単純に次のようになります。 queryset = Tweet.objects.select_related('owner').all() prefetch_related() 対照的に、 は、多対多および多対 1 の接続に使用されます。すべてのモデルを含む単一のクエリが生成され、クエリで指定されたフィルターが生成されます。 prefetch_related() 構文は単純に次のようになります。 Book.objects.prefetch_related('author').get(id=1).author.first_name 注: SQL を使用して ManyToMany リレーションシップを処理しないでください。大きなテーブルを処理するときに多くのパフォーマンスの問題が発生する可能性があるためです。そのため、prefetch_related メソッドは Python 内でテーブルを結合し、大きな SQL 結合を回避します。 と の違いについて詳しくは、 をご覧ください。 select_related() prefetch_related() こちら と の使用 bulk_create() bulk_update() は、指定されたオブジェクトのリストを 1 回のクエリでデータベースに作成するメソッドです。同様に、 は、提供されたモデル インスタンスの特定のフィールドを 1 つのクエリで更新するメソッドです。 bulk_create() bulk_update() たとえば、以下に示すような投稿モデルがあるとします。 class Post(models.Model): title = models.CharField(max_length=300, unique=True) time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title さて、このモデルに複数のデータ レコードを追加したいとしましょう。次に、次のように を使用できます。 bulk_create() #articles articles = [Post(title="Hello python"), Post(title="Hello django"), Post(title="Hello bulk")] #insert data Post.objects.bulk_create(articles) 出力は次のようになります。 >>> Post.objects.all() <QuerySet [<Post: Hello python>, <Post: Hello django>, <Post: Hello bulk>]> データを更新したい場合は、次のように を使用できます。 bulk_update() update_queries = [] a = Post.objects.get(id=14) b = Post.objects.get(id=15) c = Post.objects.get(id=16) #set update value a.title="Hello python updated" b.title="Hello django updated" c.title="Hello bulk updated" #append update_queries.extend((a, b, c)) Post.objects.bulk_update(update_queries, ['title']) 出力は次のようになります。 >>> Post.objects.all() <QuerySet [<Post: Hello python updated>, <Post: Hello django updated>, <Post: Hello bulk updated>]> 結論 この記事では、データベースのパフォーマンスを最適化し、ボトルネックを減らし、Django アプリケーションのリソースを節約するためのヒントについて説明しました。 お役に立てば幸いです。読み続けます!