Cả hai đều dùng để chống N+1 query, nhưng cho 2 loại quan hệ khác nhau. select_related(...) dành cho ForeignKey / OneToOneField (quan hệ single) — Django ghép JOIN vào 1 câu SQL, trả về object kèm related đã load.
Còn prefetch_related(...) dành cho ManyToMany và reverse ForeignKey (quan hệ multiple) — Django chạy 2 query rồi ghép lại trong Python, vì JOIN nhiều-nhiều sẽ phồng kết quả lên rất nhanh.
# 1 query, JOIN posts ⨝ authors
Post.objects.select_related('author').all()
# 2 query: lấy authors, rồi lấy mọi posts của họ rồi ghép
Author.objects.prefetch_related('post_set').all()Khi cần lọc thêm related, dùng Prefetch:
from django.db.models import Prefetch
Author.objects.prefetch_related(
Prefetch('post_set', queryset=Post.objects.filter(published=True))
)Đừng vội select_related cho mọi relation — JOIN sâu 4-5 bảng nhiều khi còn chậm hơn 2 query nhỏ.
Cài django-debug-toolbar hoặc xem connection.queries để đo trực tiếp, đừng đoán.
Both fight N+1 queries, but for different relation kinds:
select_related(...)is forForeignKey/OneToOneField(single relations). Django adds aJOINto one SQL statement and returns the object with its related row preloaded.prefetch_related(...)is forManyToManyand reverseForeignKey(multiple relations). Django runs two queries and stitches them in Python — a many-to-many JOIN would explode the result set.
# 1 query, JOIN posts ⨝ authors
Post.objects.select_related('author').all()
# 2 queries: load authors, then all their posts, then stitch
Author.objects.prefetch_related('post_set').all()When you need to filter the related rows, use Prefetch:
from django.db.models import Prefetch
Author.objects.prefetch_related(
Prefetch('post_set', queryset=Post.objects.filter(published=True))
)Pitfall: Do not select_related every relation — a 4–5 level JOIN can be slower than two small queries.
Measure with django-debug-toolbar or connection.queries.