Django只预取相关模型的最新对象



考虑以下模型:

class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, related_name="books", on_delete=models.CASCADE)
title = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)

和以下代码:

queryset = Author.objects.all()
for author in queryset:
print(author.name)
print(author.books.latest("created_at").title)

如上所述,会导致N+1查询。我试着通过预取books来修复它,如下所示:

queryset = Author.objects.prefetch_related('books')

然而,这并不能解决N+1问题。我认为原因是预取执行SELECT * FROM book WHERE author_id IN (1,2,...),这与调用.latest((执行的查询不同,即SELECT * FROM book WHERE author_id = 1 ORDER BY created_at DESC LIMIT 1。预取执行IN,.latest((执行=

我也尝试过以下方法,但没有成功:

queryset = Author.objects.prefetch_related(Prefetch('books', queryset=Book.objects.order_by("-created_at")))

在使用.latest((时,为了避免N+1选择,预取应该是什么样子?

您可以使用一些自定义代码:

from django.db.models import OuterRef, Subquery

authors = Author.objects.annotate(
last_book_id=Subquery(
Book.objects.filter(author_id=OuterRef('pk')).order_by('-created_at').values('pk')[:1]
)
)
author_dict = {author.pk: author for author in authors}
last_books = Book.objects.filter(
pk__in=[author.last_book_id for author in authors if author.last_book_id is not None]
)
for book in last_books:
author_dict[book.author_id].last_book = book

authors中的Author对象将具有一个额外的属性last_book,如果该作者至少有一本书是最后一本书。

这里的逻辑在某种程度上是Django的.prefetch_related在幕后所做的:相反,它会提取与这些作者相关的所有书籍,然后为每个对象创建一个对象,并将相关的Book对象包装到一个集合中。

最新更新