Python 查询太慢;与预回迁相关的未解决问题
我们将Django 2.1用于。我有每页显示96个用户的页面,对于每个用户,我想显示他在Speedy Match上有多少朋友,并且有一个活动的电子邮件地址。查询检查每个用户的Python 查询太慢;与预回迁相关的未解决问题,python,django,query-performance,prefetch,Python,Django,Query Performance,Prefetch,我们将Django 2.1用于。我有每页显示96个用户的页面,对于每个用户,我想显示他在Speedy Match上有多少朋友,并且有一个活动的电子邮件地址。查询检查每个用户的(self.email\u addresses.filter(is\u confirm=True).exists())是否为True: def has_confirmed_email(self): return (self.email_addresses.filter(is_confirmed=True).exist
(self.email\u addresses.filter(is\u confirm=True).exists())
是否为True:
def has_confirmed_email(self):
return (self.email_addresses.filter(is_confirmed=True).exists())
对于96个用户中的每一个用户,它会检查他的所有朋友并运行此查询——每页超过数百次。获取用户的查询是User.objects.all().order\u by()
,然后为每个用户检查此查询:
qs = self.friends.all().prefetch_related("from_user", "from_user__{}".format(SpeedyNetSiteProfile.RELATED_NAME), "from_user__{}".format(SpeedyMatchSiteProfile.RELATED_NAME), "from_user__email_addresses").distinct().order_by('-from_user__{}__last_visit'.format(SiteProfile.RELATED_NAME))
我在用户的管理器模型中添加了与预取相关的
def get_queryset(self):
from speedy.net.accounts.models import SiteProfile as SpeedyNetSiteProfile
from speedy.match.accounts.models import SiteProfile as SpeedyMatchSiteProfile
return super().get_queryset().prefetch_related(SpeedyNetSiteProfile.RELATED_NAME, SpeedyMatchSiteProfile.RELATED_NAME, "email_addresses").distinct()
但是,将“电子邮件地址”和“来自用户的电子邮件地址”添加到与预回迁相关的并不能加快页面加载速度,加载页面大约需要16秒。当加载页面而不检查每个朋友是否有已确认的电子邮件地址时,加载页面大约需要3秒钟。是否有一种方法可以一次性加载用户的所有电子邮件地址,而不是每次检查用户时加载?事实上,我也希望朋友查询加载一次,而不是每页96次(每个用户一次),但页面加载时间为3秒钟,所以这并不重要。但是如果我能查询一下friends表,那会更好
查询是由以下行()引起的:
这由is\u active\u和
调用,后者由get\u matching\u rank
调用,以检查用户是否与特定用户匹配。这是通过模型中的方法get\u friends
调用的
更新#1:如果我在模型中的def已确认电子邮件(…)
中更改为return True
,则页面加载速度仅快3秒(13秒而不是16秒),因此此页面中可能存在更多与性能相关的问题
如果我禁用get\u matching\u rank
的功能,并将其替换为普通的return 5
,页面加载速度会快得多。但是我们当然需要这个函数的功能。当为两个特定用户的集合调用此函数时,我们是否可以将其结果缓存几分钟
更新#2:我想在用户模型中添加一个布尔字段,如果用户有一个确认的电子邮件地址,则该字段将为真。该字段将在每次保存或删除电子邮件地址时更新。我知道如何覆盖save方法,但是当电子邮件地址被删除时,如何更新此字段?它也可能被管理员删除
我认为我应该使用诸如post\u save
和post\u delete
这样的信号,以便预回迁产生任何效果,您必须在用户模型上使用它-很难从您所包含的内容判断您是否在这样做
如果不为每个用户预取好友,则执行self.friends.all()
将导致查询。要使用预回迁绕过查询,可以执行以下操作之一:
User.objects.prefetch_related('friends')
或者可以使用预取
对象进一步筛选:
User.objects.prefetch_related(Prefetch(
'friends',
queryset=Friend.objects.filter(is_confirmed=True)
)
使用filter关键字参数的Count
注释会快得多
from djang.db.models import Count, Q
qs = User.objects.annotate(
friend_count=Count('friends', filter=Q(friends__is_confirmed=True)
)
要使预回迁产生任何效果,您必须在用户模型上使用它——很难从您所包含的内容判断您是否在这样做
如果不为每个用户预取好友,则执行self.friends.all()
将导致查询。要使用预回迁绕过查询,可以执行以下操作之一:
User.objects.prefetch_related('friends')
或者可以使用预取
对象进一步筛选:
User.objects.prefetch_related(Prefetch(
'friends',
queryset=Friend.objects.filter(is_confirmed=True)
)
使用filter关键字参数的Count
注释会快得多
from djang.db.models import Count, Q
qs = User.objects.annotate(
friend_count=Count('friends', filter=Q(friends__is_confirmed=True)
)
我在用户模型中添加了一个字段:
has_confirmed_email = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
if ((self.is_staff) or (self.is_superuser)):
warnings.warn('Can’t delete staff user.')
return False
else:
self.email_addresses.all().delete() # This is necessary because of the signal above.
return super().delete(*args, **kwargs)
方法是:
def _update_has_confirmed_email_field(self):
self.has_confirmed_email = (self.email_addresses.filter(is_confirmed=True).count() > 0)
self.save_user_and_profile()
以及:
在用户模型中:
has_confirmed_email = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
if ((self.is_staff) or (self.is_superuser)):
warnings.warn('Can’t delete staff user.')
return False
else:
self.email_addresses.all().delete() # This is necessary because of the signal above.
return super().delete(*args, **kwargs)
我还从管理员视图中删除了好友数,现在管理员视图页面在大约1.5秒内加载。我在用户模型中添加了一个字段:
has_confirmed_email = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
if ((self.is_staff) or (self.is_superuser)):
warnings.warn('Can’t delete staff user.')
return False
else:
self.email_addresses.all().delete() # This is necessary because of the signal above.
return super().delete(*args, **kwargs)
方法是:
def _update_has_confirmed_email_field(self):
self.has_confirmed_email = (self.email_addresses.filter(is_confirmed=True).count() > 0)
self.save_user_and_profile()
以及:
在用户模型中:
has_confirmed_email = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
if ((self.is_staff) or (self.is_superuser)):
warnings.warn('Can’t delete staff user.')
return False
else:
self.email_addresses.all().delete() # This is necessary because of the signal above.
return super().delete(*args, **kwargs)
我还从管理员视图中删除了好友数,现在管理员视图页面的加载时间约为1.5秒
但是将“电子邮件地址”和“来自用户的电子邮件地址”添加到预回迁相关的
并不能加快页面加载速度
这是因为self.email\u addresses.filter(is\u confirm=True).exists()
不使用预取的QuerySet
要使用预取的self.email\u地址
,请在内存中进行筛选:
def已确认电子邮件(self):
如果self.email\u addresses.all().\u result\u缓存不是无:
返回任何(电子邮件地址。已确认self.email\u addresses.all()中的电子邮件地址)
return(self.email\u addresses.filter(is\u confirm=True).exists())
注意:如果没有预取,那么改进的实现仍然会在每个已确认的\u电子邮件
函数调用中命中数据库,因为.filter
仍然会创建一个新的查询集
。为了处理这个问题,make已经确认了一封电子邮件
一个Django@cached\u属性
解释
发件人:
请记住,与QuerySets
一样,任何暗示不同数据库查询的后续链接方法都将忽略以前缓存的结果,并使用新的数据库查询检索数据
>pizzas=Pizza.objects.prefetch\u相关('toppings'))
>>>[比萨饼中比萨饼的列表(pizza.toppings.filter(spice=True))
。。。预取缓存在这里无能为力;事实上,这会影响性能,因为您已经完成了一个尚未使用的数据库查询。因此,请谨慎使用此功能
但是将“电子邮件地址”和“来自用户的电子邮件地址”添加到预回迁相关的
并不能加快页面加载速度
这是因为self.email\u addresses.filter(is\u confirm=True).exists()
不使用预取的QuerySet
使用预设置