Django Admin预取不起作用 - 重复查询〜1000次

时间:2018-01-24 09:52:28

标签: python django

我已经向django admin添加了预取,因为我注意到它正在为模型运行近1000个查询。

然而,预取似乎没有效果。据我所知,预取查询是否正确?

示例重复:

SELECT "sites_sitedata"."id", "sites_sitedata"."location", "sites_sitedata"."
 ... 
FROM "sites_sitedata" WHERE "sites_sitedata"."id" = '7'
  Duplicated 314 times. 
0.09126367597049141%
0.24    
Sel Expl
Connection: default
/itapp/itapp/circuits/models.py in __str__(88)
  return '%s | %s | %s | %s ' % (self.site_data.location, \

电路供应商和电路类型也有重复的高级别

admin.py

class SiteSubnetsAdmin(admin.ModelAdmin):
    search_fields = ['site_data','device_data','subnet','subnet_type','vlan_id','peer_desc'] 
    list_display = ('site_data','device_data','subnet','subnet_type','vlan_id','peer_desc')
    ordering = ('site_data','device_data',)

    def get_queryset(self, request):
        queryset = super(SiteSubnetsAdmin, self).get_queryset(request)
        queryset = SiteSubnets.objects \
                .prefetch_related( 
                    Prefetch(
                        'circuit',
                        queryset=Circuits.objects.prefetch_related('site_data').prefetch_related('service_contacts').prefetch_related('circuit_type').prefetch_related('provider'),
                    ),
                ) \
                .prefetch_related('site_data') \
                .prefetch_related('device_data') \
                .prefetch_related('subnet_type')
        return queryset

admin.site.register(SiteSubnets, SiteSubnetsAdmin)

subnets.models

class SiteSubnets(models.Model):
    device_data = models.ForeignKey(DeviceData, verbose_name="Device", \
                on_delete=models.PROTECT, blank=True, null=True)
    site_data = models.ForeignKey(SiteData, verbose_name="Location", \
                on_delete=models.PROTECT, blank=True, null=True)               
    subnet = models.GenericIPAddressField(protocol='IPv4', \
             verbose_name="Subnet", blank=True, null=True)
    subnet_type = models.ForeignKey(SubnetTypes, verbose_name="Subnet Type") 
    circuit = models.ForeignKey(Circuits, verbose_name="Link to circuit?", \
                on_delete=models.PROTECT, blank=True, null=True)
    vlan_id = models.IntegerField(verbose_name="Vlan ID", blank=True, null=True)
    peer_desc = models.IntegerField(verbose_name="Peer description", blank=True, null=True)
    class Meta:
        verbose_name = "Site Subnets"
        verbose_name_plural = "Site Subnets"

    def __str__(self):
        if self.device_data != None:
            return '{0} - {1} - {2}'.format(self.site_data,self.device_data, self.subnet)       
        else:
            return '{0} - {1}'.format(self.site_data, self.subnet) 

circuits.models

class Circuits(models.Model):
    site_data = models.ForeignKey(SiteData, verbose_name="Site", on_delete=models.PROTECT)
    order_no = models.CharField(max_length=200, verbose_name="Order No")
    expected_install_date = models.DateField()
    install_date = models.DateField(blank=True, null=True)
    circuit_type = models.ForeignKey(CircuitTypes, verbose_name="Circuit Type", on_delete=models.PROTECT)
    circuit_preference = models.CharField(max_length=20, verbose_name="Circuit Preference", \
                         choices=settings.CIRCUIT_PREFERENCE, blank=True, null=True)
    provider = models.ForeignKey(CircuitProviders, verbose_name="Circuit Provider", on_delete=models.PROTECT)
    service_contacts = models.ForeignKey(ServiceContacts, on_delete=models.PROTECT)
    subnet_mask = models.CharField(max_length=4, verbose_name="Subnet Mask", \
                  choices=settings.SUBNET_MASK_CHOICES, blank=True, null=True)
    decommissioned = models.BooleanField(default=False, verbose_name="Decomission this circuit?")
    active_link = models.BooleanField(default=False, verbose_name="Active Link?")


    class Meta:
        verbose_name = "Circuit Data"
        verbose_name_plural = "Circuit Data"
        permissions = (
            ("can_view_financial", "Can View Financial"),
            ("can_view_orders", "Can View Orders"),
        )

    def __str__(self):
        return '%s | %s | %s | %s ' % (self.site_data.location, \
                                       self.provider, self.circuit_type, self.ref_no) 

修改 这是单独存储的方法吗?在相同的模型中还是需要在模型的外部?

def full_info(self): 
    return '{} | {} | {} | {}'.format(self.site_data.location, \
                                   self.provider, self.circuit_type, self.ref_no)     

def __str__(self):
    return '{} | {} | {}'.format(self.provider, self.circuit_type, self.ref_no)

2 个答案:

答案 0 :(得分:2)

您应该使用select_related作为转发外键。 prefetch_related预取反向外键和多对多关系:

# ...
queryset=Circuits.objects\
    .select_related('site_data', 'service_contacts', 'circuit_type', 'provider'),
# ...
.select_related('site_data', 'device_data', 'subnet_type')

__str__中显示所有这些fk信息可能很麻烦,因为默认情况下在管理员的多个位置使用__str__,例如更改列表,但也包括表单输入下拉列表。您可以显示相关对象的pk(如果信息足够),因为它存储在本地表中并且不会创建额外的查询:

def __str__(self):
    return '%s | %s | %s | %s ' % (self.site_data_id, \
                                   self.provider_id, self.circuit_type_id, self.ref_no) 

答案 1 :(得分:1)

首先,我会尽量避免在__str__方法中包含外键。它可能会导致大量的查询。保持__str__方法尽可能简单,如果需要显示相关信息,则创建另一种方法并在必要时使用。

其次,我不确定Django是否支持在prefetch_related对象中使用Prefetch。在您的情况下,您甚至不需要使用prefetch_related。对于外键,您可以使用select_related,Django将进行连接以获取相关对象,而不是在单独的查询中预取它们。

queryset = SiteSubnets.objects.select_related(
    'circuit', 'circuit__site_data', 'circuit__service_contacts', 'circuit__circuit_type', 'circuit__provider'
    ).select_related('site_data', 'device_data', subnet_type')

如果其他查询来自Django管理员中外键的选择字段,那么优化get_queryset并不会有所帮助。您可以按照我的建议简化__str__方法,深入研究Django管理员内部以尝试优化查询,使用ajax小部件来阻止加载所有选项,或者忍受大量查询。