Django ORM exclude方法异常:filter正常但exclude卡顿无响应
Django ORM中exclude()方法异常排查:filter正常但exclude导致CPU满载
嘿,我来帮你拆解下这个问题——你遇到的情况在Django ORM处理关联查询时其实挺常见的,尤其是在数据量不小的场景下。先理清楚前因后果:
你的模型定义
class Dictionary(DateTimeModel): base_word = models.ForeignKey(BaseDictionary, related_name=_('dict_words')) word = models.CharField(max_length=64) version = models.ForeignKey(Version) class FrequencyData(DateTimeModel): word = models.ForeignKey(Dictionary, related_name=_('frequency_data')) count = models.BigIntegerField(null=True, blank=True) source = models.ForeignKey(Source, related_name=_('frequency_data'), null=True, blank=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name=_('frequency_data')) user_ip_address = models.GenericIPAddressField(null=True, blank=True) date_of_checking = models.DateTimeField(null=True, blank=True) is_checked = models.BooleanField(default=False)
问题场景
你想要获取关联的FrequencyData不属于用户1的Dictionary数据:
Dictionary.objects.prefetch_related('frequency_data').filter(frequency_data__user=1)[:100]运行完全正常Dictionary.objects.prefetch_related('frequency_data').exclude(frequency_data__user=1)[:100]直接把CPU拉满,持续加载无响应
而且哪怕去掉prefetch_related,问题依然存在。补充下你提供的关键信息:Dictionary表有120k条数据,FrequencyData表有160k条数据,使用PostgreSQL 9.6.6。
为什么exclude()会出问题?
核心原因是Django ORM对反向关联exclude的默认SQL生成逻辑,在数据量大时效率极低:
filter(frequency_data__user=1)生成的SQL是做内连接,只保留存在至少一条匹配用户1的FrequencyData的Dictionary,逻辑简单,数据库能高效处理。- 但
exclude(frequency_data__user=1)生成的SQL是做左连接,然后排除匹配用户1的行。这会导致两个问题:- 左连接会生成大量重复行(一个Dictionary如果有多个FrequencyData,就会对应多行结果)
- Django需要对这些重复行进行去重操作,当表数据量达到120k+160k级别时,这种去重计算会直接把CPU资源耗尽。
更关键的是,这个写法的逻辑其实和你想要的不一样——它会返回那些存在至少一条非用户1的FrequencyData的Dictionary,同时包含完全没有FrequencyData的Dictionary,但执行效率完全扛不住数据量。
高效的解决方案
推荐两种高效的写法,都能避免左连接带来的性能问题:
方法1:使用Exists子查询(Django 1.11+支持)
这种写法直接检查每个Dictionary是否存在用户1的FrequencyData,生成的SQL效率极高:
from django.db.models import Exists, OuterRef # 标注每个Dictionary是否关联了用户1的FrequencyData,然后排除这些项 Dictionary.objects.annotate( has_user1_freq=Exists( FrequencyData.objects.filter( word=OuterRef('pk'), user=1 ) ) ).exclude(has_user1_freq=True)[:100]
方法2:先获取关联ID再排除
先找出所有被用户1的FrequencyData关联的Dictionary ID,再排除这些ID:
# 先拿到所有关联用户1的Dictionary的ID列表 user1_dict_ids = FrequencyData.objects.filter(user=1).values_list('word_id', flat=True) # 排除这些ID的Dictionary Dictionary.objects.exclude(pk__in=user1_dict_ids)[:100]
这两种方法都能让PostgreSQL高效处理查询,不会出现CPU满载的情况。
内容的提问来源于stack exchange,提问作者Mike Br
相关产品推荐
相关产品推荐

