用于迭代嵌套结果的 Django 查询集预取优化
Posted
技术标签:
【中文标题】用于迭代嵌套结果的 Django 查询集预取优化【英文标题】:Django Queryset Prefetch Otimization for iterating over nested results 【发布时间】:2016-07-21 07:32:22 【问题描述】:考虑到我想要获取嵌套关系,我正在寻找一种通过提高数据库访问性能来优化 Django 中查询集结果处理的方法。
例如,我做了这样的结构:
class Movie(models.Model):
name = models.CharField(max_length=50)
class Ticket(models.Model):
code = models.CharField(max_length=255, blank=True, unique=True)
movie = models.ForeignKey(Movie, related_name='tickets')
class Buyer(models.Model):
name = models.CharField(max_length=50)
class Purchase(models.Model):
tickets = models.ManyToManyField(Ticket, related_name='purchases')
buyer = models.ForeignKey(Buyer, related_name='purchases')
假设我有一个电影查询集:
movies = Movie.objects.all().prefetch_related('tickets__purchases__buyer')
如果我想在这个 qs 中检索每部电影的所有买家,我可以这样做:
for movie in movies:
buyers = Buyer.objects.filter(purchases__tickets__in=movie.tickets.all()).distinct()
但是在这种方法中,它会为每个迭代的电影再次访问数据库。要解决此问题,我执行以下操作:
def get_movie_buyers(movie):
buyers = set()
for ticket in movie.tickets.all():
for purchase in ticket.purchases.all():
if purchase.buyer:
buyers.add(purchase.buyer)
return buyers
for movie in movies:
buyers = get_movie_buyers(movie)
# do something with the buyers
这种方式只访问数据库一次,因为我之前使用过 prefetch_related,但我认为这不是一个好方法,因为我必须迭代许多嵌套循环,这会增加内存过载。
我认为有更好的方法,但我仍然没有找到“正确”的方法,希望有人能指导我。
更新
正如alasdair 提到的,要使用 Prefetch 对象,但我已经尝试使用以下内容:
movies = Movie.objects.prefetch_related(
Prefetch(lookup='tickets__purchases__buyer',
to_attr='buyers')
).all()
for movie in movies:
print movie.buyers
这给了我以下错误:
'Movie' object has no attribute 'buyers'
【问题讨论】:
我认为您可以使用Prefetch()
对象来做到这一点。
我尝试过使用 Prefetch() 对象:Prefetch(lookup='tickets__purchases__buyer', to_attr='buyers'),但我不知道如何构建 queryset 参数,因为询问的买家与自己的电影有关。你能给我举个例子吗?
对不起,我不知道具体如何处理你的情况,我留下了评论,因为我认为这可能。
尝试在prefetch_related
调用之后添加.all()
即使使用.all()
我也会得到 AttributeError
【参考方案1】:
看起来太难的原因是购买和门票之间的多对多关系。
这种关系允许在多次购买中出现同一张票。但实际数据并非如此,因为一张票只能购买一次。 如果您删除此 ManyToMany 字段并在 Ticket to purchase 中添加 ForeignKey 字段,则可以简化查询
class Ticket(models.Model):
code = models.CharField(max_length=255, blank=True, unique=True)
movie = models.ForeignKey(Movie, related_name='tickets')
purchase = models.ForeignKey(Purchase, null=True, blank=True)
那么查询可以简化如下。
movies = Movie.objects.all().prefetch_related('tickets__purchase__buyer')
for movie in movies:
print(set(ticket.purchase.buyer for ticket in movie.tickets if ticket.purchase))
这肯定会在创建购买时增加额外的复杂性,因为您需要使用 purchase_id 更新票证对象。
您需要根据两个操作的频率来确定在何处保持复杂性
【讨论】:
以上是关于用于迭代嵌套结果的 Django 查询集预取优化的主要内容,如果未能解决你的问题,请参考以下文章