Django Admin中FriendlyMatch创建页查询优化方案咨询
你的问题典型是N+1查询导致的:Player模型的__str__方法需要访问关联的User对象的username,而Django Admin在渲染FriendlyMatch创建页面的Player外键下拉框时,会加载所有Player记录,每加载一个Player就会单独发起一次SQL查询去获取对应的User数据。如果Player数量多,再加上FriendlyMatch里有多个Player外键字段(player_1、player_2、各局胜者、最终胜者),就会触发大量重复查询,直接拖慢页面加载。
要保留__str__方法的同时解决这个问题,核心是让Django一次性预加载Player关联的User数据,避免每次单独查询。具体可以通过修改FriendlyMatch的Admin配置实现:
方法一:优化外键字段的查询集
在FriendlyMatchAdmin中重写formfield_for_foreignkey方法,给所有关联Player的外键字段添加select_related('user'),让Django在查询Player时自动关联查询User:
@admin.register(FriendlyMatch) class FriendlyMatchAdmin(admin.ModelAdmin): list_display = ("id", "date_start", "player_1", "player_2", "status_friendly_match") def formfield_for_foreignkey(self, db_field, request, **kwargs): # 针对所有关联Player的外键字段优化查询 if db_field.remote_field.model == Player: kwargs["queryset"] = Player.objects.select_related("user") return super().formfield_for_foreignkey(db_field, request, **kwargs)
方法二:全局优化Player的默认查询集(可选)
如果你的项目中很多地方都需要Player关联User,也可以在Player模型的Manager里默认加上select_related('user'),这样所有查询Player的地方都会自动预加载User:
class PlayerManager(models.Manager): def get_queryset(self): return super().get_queryset().select_related("user") class Player(models.Model): """Модель игрока""" user = models.ForeignKey( User, related_name="player", on_delete=models.CASCADE, verbose_name="Игрок" ) created = models.DateTimeField(auto_now_add=True) team = models.ForeignKey( Team, related_name="team_player", null=True, on_delete=models.SET_NULL, verbose_name="Команда", ) class Meta: verbose_name = "Игрок" verbose_name_plural = "Игроки" objects = PlayerManager() def __str__(self): return f"Игрок {self.user.username}"
不过这种方法要注意,如果有不需要User的场景,可能需要手动用defer或者only排除,但对于你的Admin场景来说完全适用。
额外优化:列表页查询优化
虽然你的问题是创建页面,但顺便优化下列表页的查询,避免列表页也出现N+1:
@admin.register(FriendlyMatch) class FriendlyMatchAdmin(admin.ModelAdmin): list_display = ("id", "date_start", "player_1", "player_2", "status_friendly_match") def get_queryset(self, request): return super().get_queryset(request).select_related( "player_1__user", "player_2__user" # 如果列表页需要显示其他胜者字段,也可以加上对应的__user ) def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.remote_field.model == Player: kwargs["queryset"] = Player.objects.select_related("user") return super().formfield_for_foreignkey(db_field, request, **kwargs)
select_related是Django用来优化一对一/外键关联查询的方法,它会通过SQL的JOIN语句一次性把关联表的数据查询出来,替代原来的多次单表查询,直接把查询次数从几千次降到几次,大幅提升页面加载速度。
内容的提问来源于stack exchange,提问作者Niolum

