Django でパフォーマンスを向上させる: 'get_list_select_related' を使いこなしてデータベースクエリを減らす


admin.ModelAdmin.get_list_select_related() は、Django 管理画面におけるリスト表示で関連するモデルを効率的に取得するためのメソッドです。このメソッドを活用することで、データベースへのクエリ回数を削減し、パフォーマンスを向上させることができます。

Django 管理画面でモデルのリストを表示する場合、デフォルトでは関連するモデルも同時に取得されます。しかし、多くの関連モデルが存在する場合、データベースへのクエリ回数が多くなり、パフォーマンスが低下する可能性があります。

get_list_select_related() メソッドを使用することで、リスト表示に必要な関連モデルのみを明示的に指定することができます。これにより、データベースへのクエリ回数を削減し、パフォーマンスを向上させることができます。

使用方法

get_list_select_related() メソッドは、ModelAdmin クラスのメソッドとして定義されます。このメソッドは、関連するモデル名をカンマ区切りの文字列として引数に渡します。

class MyModelAdmin(admin.ModelAdmin):
    def get_list_select_related(self):
        return 'author', 'category'

上記の例では、MyModelauthorcategory という関連モデルがリスト表示に必要な関連モデルと指定されています。

注意点

  • 関連モデルが多すぎる場合、get_list_select_related() メソッドを使用してもパフォーマンスが向上しない場合があります。
  • 関連モデルが null=True の場合、get_list_select_related() メソッドで明示的に指定しないと取得されない場合があります。
  • get_list_select_related() メソッドは、Django 1.9 以降で使用できます。

get_list_select_related() メソッドは、リスト表示のパフォーマンスを向上させるための有効な手段ですが、状況によっては他の方法の方が適切な場合があります。例えば、関連モデルの数が少ない場合や、関連モデルが頻繁に変更される場合は、get_list_select_related() メソッドを使用するよりも、デフォルトの動作のままの方が効率的な場合があります。



from django.contrib import admin

from .models import MyModel


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author', 'category', 'created_at']

    def get_list_select_related(self):
        return 'author', 'category'

例2:関連モデルの一部のみを取得

from django.contrib import admin

from .models import MyModel


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author', 'category', 'created_at']

    def get_list_select_related(self):
        return 'author'

例3:動的に関連モデルを指定

from django.contrib import admin

from .models import MyModel


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author', 'category', 'created_at']

    def get_list_select_related(self, request):
        if request.GET.get('author'):
            return 'author'
        else:
            return 'category'

説明

上記の例では、MyModel というモデルの管理画面におけるリスト表示で get_list_select_related() メソッドを使用する例を示しています。

例1 では、authorcategory という関連モデルをすべて取得しています。

例2 では、author という関連モデルのみを取得しています。

例3 では、リクエストパラメータ author の有無に応じて、取得する関連モデルを動的に変更しています。

これらの例は、get_list_select_related() メソッドをどのように使用できるかを示すほんの一例です。具体的な使用方法は、状況に応じて調整する必要があります。

関連モデルがネストしている場合

from django.contrib import admin

from .models import MyModel, Author, Category


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author__name', 'category__name', 'created_at']

    def get_list_select_related(self):
        return 'author', 'author__category'

関連モデルが複数存在する場合

from django.contrib import admin

from .models import MyModel, Author, Category, Tag


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author__name', 'category__name', 'tags__name', 'created_at']

    def get_list_select_related(self):
        return 'author', 'author__category', 'tags'
from django.contrib import admin

from .models import MyModel, Author, Category


class MyModelAdmin(admin.ModelAdmin):
    list_display = ['name', 'author__username', 'category__slug', 'created_at']

    def get_list_select_related(self):
        return 'author', 'category'


以下に、get_list_select_related() メソッドの代替方法をいくつか紹介します。

prefetch_related() を使用する

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=255)
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)

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

class Category(models.Model):
    name = models.CharField(max_length=255)


def my_view(request):
    queryset = MyModel.objects.all().prefetch_related('author', 'category')
    return render(request, 'my_template.html', {'objects': queryset})

カスタムクエリを使用する

get_list_select_related() メソッドよりも複雑なクエリが必要な場合は、カスタムクエリを使用することができます。

from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=255)
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)

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

class Category(models.Model):
    name = models.CharField(max_length=255)


def my_view(request):
    queryset = MyModel.objects.select_related('author', 'category').filter(created_at__gt=F('updated_at'))
    return render(request, 'my_template.html', {'objects': queryset})

キャッシュを使用する

関連モデルが頻繁にアクセスされる場合は、キャッシュを使用することでパフォーマンスを向上させることができます。

from django.core.cache import cache

def my_view(request):
    cached_queryset = cache.get('my_model_list')
    if cached_queryset is None:
        queryset = MyModel.objects.all().select_related('author', 'category')
        cache.set('my_model_list', queryset, 60 * 10)  # 10分間キャッシュ
    else:
        queryset = cached_queryset

    return render(request, 'my_template.html', {'objects': queryset})

非同期処理を使用する

関連モデルの取得処理が重い場合は、非同期処理を使用することでパフォーマンスを向上させることができます。

from django.db import models
from django.dispatch import receiver
from django.db.models.signals import post_save

class MyModel(models.Model):
    name = models.CharField(max_length=255)
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)

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

class Category(models.Model):
    name = models.CharField(max_length=255)


@receiver(post_save, sender=MyModel)
def async_update_related_models(instance, **kwargs):
    from django.core.cache import cache
    cache.delete('my_model_list')  # キャッシュを削除

def my_view(request):
    queryset = MyModel.objects.all().select_related