Django多对多查询性能问题:900万条数据查询耗时超15分钟
问题根源
- Django默认自动生成的多对多中间表索引结构不符合查询场景:默认生成的中间表仅会创建
(follower_id, hashtag_id)的联合唯一约束/索引,如果你执行的是从Hashtag反向查询关联的Follower的场景,该联合索引无法生效,会触发中间表全表扫描,900万条数据的全表扫描耗时自然会极高。 - ORM查询写法不规范:大概率存在N+1查询问题,比如遍历Follower查询集时每次访问
hashtags属性都触发一次单独的数据库查询,或者未使用预加载接口加载关联数据,导致数据库查询次数随结果集线性增长。 - 过滤条件未命中索引:如果查询时用到了
hashtags__name这类跨表过滤条件,未命中索引也会触发主表全表扫描。
优化方案
1. 索引优先优化
手动定义多对多中间表,按需创建索引,替换Django自动生成的默认中间表:
# 新增中间表模型 class FollowerHashtag(models.Model): follower = models.ForeignKey("Follower", on_delete=models.CASCADE) hashtag = models.ForeignKey("instagram_data.Hashtag", on_delete=models.CASCADE) class Meta: unique_together = ("follower", "hashtag") indexes = [ # 适配从Follower查关联Hashtag的场景 models.Index(fields=["follower", "hashtag"]), # 适配从Hashtag反向查关联Follower的场景 models.Index(fields=["hashtag", "follower"]), ] # 修改原Follower模型的多对多字段配置 class Follower(models.Model): hashtags = models.ManyToManyField( "instagram_data.Hashtag", through=FollowerHashtag, verbose_name=_("hashtags_name") )
执行migrate生效后,双向的关联查询都可以命中索引,避免全表扫描。
2. ORM查询写法优化
- 多对多关联查询必须使用
prefetch_related预加载关联数据,彻底避免N+1问题:# 示例:查询符合条件的Follower及关联的Hashtag follower_list = Follower.objects.filter(你的过滤条件).prefetch_related("hashtags") - 仅需要部分字段时,用
only/values减少数据加载量,比如只需要Hashtag的name字段时:from django.db.models import Prefetch follower_list = Follower.objects.filter(你的过滤条件).prefetch_related( Prefetch("hashtags", queryset=Hashtag.objects.only("name")) ) - 反向查询仅需要关联ID时,直接查中间表即可,无需关联主表:
# 示例:查询某Hashtag关联的所有Follower ID follower_ids = FollowerHashtag.objects.filter(hashtag_id=目标hashtag的id).values_list("follower_id", flat=True)
3. 大数据量进阶优化(适配后续2亿条数据规模)
- 高频查询场景增加缓存层:把经常查询的关联关系(比如热门Hashtag对应的Follower列表)存入Redis,设置合理的过期时间,避免每次请求都查数据库。
- 模糊搜索场景引入全文检索引擎:如果有按hashtag名称模糊搜索的需求,把Hashtag的name字段同步到Elasticsearch,先通过ES拿到符合条件的Hashtag ID,再查中间表取关联数据,比数据库的
LIKE模糊查询性能提升10倍以上。 - 水平分表:数据量达到亿级后,按follower_id或者hashtag_id对中间表做水平拆分,降低单表数据量,避免单表性能瓶颈。
内容的提问来源于stack exchange,提问作者flice com
相关产品推荐
相关产品推荐

