select_related does SQL JOIN. prefetch_related does separate queries for related objects.
1from django.db import models23class Author(models.Model):4 name = models.CharField(max_length=100)56class Book(models.Model):7 title = models.CharField(max_length=200)8 author = models.ForeignKey(Author, on_delete=models.CASCADE)9 tags = models.ManyToManyField("Tag")1011class Tag(models.Model):12 name = models.CharField(max_length=50)1314# Without optimization — N+1 queries!15for book in Book.objects.all():16 print(book.author.name) # Extra query each time!1718# select_related — JOIN (ForeignKey, OneToOne)19for book in Book.objects.select_related("author").all():20 print(book.author.name) # No extra query2122# prefetch_related — separate query (ManyToMany)23for book in Book.objects.prefetch_related("tags").all():24 print(book.tags.all()) # Tags prefetched2526# Chaining27books = (28 Book.objects29 .select_related("author")30 .prefetch_related("tags")31 .filter(author__name="Alice")32)3334# Prefetch with custom queryset35from django.db.models import Prefetch3637books = Book.objects.prefetch_related(38 Prefetch("tags", queryset=Tag.objects.filter(name__startswith="D"))39)
Rules:
select_related — ForeignKey, OneToOne.prefetch_related — ManyToMany, reverse FK.