記事一覧へ戻る 本の順番で続きを読む

データベース設計と最適化 - パフォーマンスを考慮した設計

Python3上級 | 2026/02/18 21:20

データベース設計と最適化

インデックス設計

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)  # unique=True で自動インデックス
    status = models.CharField(max_length=10, db_index=True)  # 単独インデックス
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)  # FK自動インデックス

    class Meta:
        indexes = [
            models.Index(fields=['status', '-created_at']),  # 複合インデックス
            models.Index(
                fields=['status'],
                condition=models.Q(status='published'),
                name='published_idx'  # 部分インデックス
            ),
        ]

N+1問題と対策

# NG: N+1問題(記事ごとにSQLが発行される)
for post in Post.objects.all():
    print(post.author.username)  # 毎回SELECT

# OK: select_related(JOINで一括取得、ForeignKey用)
for post in Post.objects.select_related('author').all():
    print(post.author.username)  # 追加SQLなし

# OK: prefetch_related(別クエリで一括取得、M2M/逆引き用)
for author in User.objects.prefetch_related('posts').all():
    print(author.posts.count())  # 追加SQLなし

クエリの最適化

# only(): 必要なフィールドだけ取得
posts = Post.objects.only('title', 'slug', 'created_at')

# defer(): 特定フィールドを遅延読み込み
posts = Post.objects.defer('body')  # bodyは後でアクセス時に取得

# values(): 辞書で取得(ORMオブジェクトを作らない)
titles = Post.objects.values('title', 'created_at')

# values_list(): タプルやフラットリストで取得
ids = Post.objects.values_list('id', flat=True)

# exists(): 存在チェック(COUNTより高速)
if Post.objects.filter(author=user).exists():
    pass

# count(): COUNT集約
total = Post.objects.filter(status='published').count()

# bulk_create(): 一括挿入
posts = [Post(title=f'記事{i}', author=user) for i in range(100)]
Post.objects.bulk_create(posts)

# bulk_update(): 一括更新
posts = Post.objects.filter(status='draft')
for p in posts:
    p.status = 'published'
Post.objects.bulk_update(posts, ['status'])

SQLの確認

from django.db import connection

# クエリセットのSQL確認
qs = Post.objects.filter(status='published')
print(qs.query)

# 実行されたSQL一覧
print(connection.queries)

# django-debug-toolbar でGUIで確認
pip install django-debug-toolbar

トランザクション

from django.db import transaction

# デコレータ
@transaction.atomic
def transfer_money(from_acc, to_acc, amount):
    from_acc.balance -= amount
    from_acc.save()
    to_acc.balance += amount
    to_acc.save()

# コンテキストマネージャ
def complex_operation():
    with transaction.atomic():
        # この中の操作は全て成功するか、全て巻き戻される
        obj1.save()
        obj2.save()

まとめ

  • よく検索するフィールドにインデックスを設定
  • select_related / prefetch_related でN+1問題を防止
  • only() / defer() / values() で不要なデータ取得を抑制
  • bulk_create / bulk_update で大量データ操作を高速化
  • transaction.atomic でデータ整合性を保証