You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Admin中select_related预查询仍产生额外SQL查询的问题解决

问题分析与解决方案

你的核心问题在于:资产ModelAdmin中定义的select_related只作用于资产列表页的查询,但Django Admin的Product列表过滤器生成选项时,会单独查询Product模型,完全不会复用资产的queryset,所以你加的预加载根本覆盖不到过滤器里的Product实例,自然会触发大量N+1查询。

下面给你几个可行的解决办法:


方案一:自定义预加载关联对象的Product过滤器

直接继承Django的SimpleListFilter,在生成过滤器选项时提前预加载category和company,一次性把关联数据查出来:

from django.contrib.admin import SimpleListFilter
from .models import Product

class ProductFilter(SimpleListFilter):
    title = '产品'
    parameter_name = 'product'

    def lookups(self, request, model_admin):
        # 预加载category和company,避免生成选项时触发额外查询
        product_list = Product.objects.select_related('category', 'company').all()
        return [(product.id, str(product)) for product in product_list]

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(product_id=self.value())
        return queryset

然后在资产的ModelAdmin中替换默认过滤器:

@admin.register(Asset)
class AssetAdmin(admin.ModelAdmin):
    # 把原来的'product'换成自定义过滤器
    list_filter = (ProductFilter, )
    # 其他配置...

方案二:重写默认关联过滤器,强制预加载

如果不想完全自定义过滤器,可以继承Django默认的RelatedFieldListFilter,重写它的查询逻辑,给Product的查询集加上预加载:

from django.contrib.admin.filters import RelatedFieldListFilter

class PreloadedProductFilter(RelatedFieldListFilter):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        # 仅针对product字段的过滤器,预加载关联对象
        if self.field.name == 'product':
            return qs.select_related('category', 'company')
        return qs

然后在资产ModelAdmin中指定使用这个过滤器:

@admin.register(Asset)
class AssetAdmin(admin.ModelAdmin):
    list_filter = (
        ('product', PreloadedProductFilter),
        # 其他过滤器...
    )
    # 其他配置...

方案三:给Product的关联属性加缓存

如果不想改过滤器逻辑,可以用Django的cached_property缓存关联对象的属性访问,减少重复查询:

from django.utils.functional import cached_property
from django.db import models

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    company = models.ForeignKey(Company, on_delete=models.SET_NULL, null=True, blank=True)
    name = models.CharField(max_length=100)

    @cached_property
    def category_name(self):
        return self.category.name

    @cached_property
    def company_name(self):
        return self.company.name if self.company else ''

    def __str__(self):
        if self.company:
            return f"{self.category_name} | {self.company_name} | {self.name}"
        return f"{self.category_name} | {self.name}"

这个方法会缓存每个Product实例的关联属性值,第一次访问时查询一次,后续不再触发SQL,但相比前两种预加载的方案,在过滤器选项数量多的时候,效率还是略低。


内容的提问来源于stack exchange,提问作者nigel239

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:31:58