Django Rest Framework含日期注解查询集响应过慢问题求助
问题:DRF中注解加日期过滤后响应超时,关联预加载引发性能问题
我在使用Django Rest Framework时,需要筛选出关联项数量大于0的EventVenue对象,但在annotate的Count过滤器中加入日期时间条件后,响应时间从毫秒级骤增至约90秒。
耗时代码
class EventVenueViewSet(viewsets.ReadOnlyModelViewSet): queryset = EventVenue.objects.prefetch_related("eventblock_set__eventslot_set__event").annotate( num_slots=models.Count( "eventblock__eventslot", filter=Q(eventblock__eventslot__event=None) & Q( eventblock__eventslot__start_time__gt=timezone.now() + timedelta(days=2) ) ) ).filter(num_slots__gt=0)
移除日期过滤后的正常代码
class EventVenueViewSet(viewsets.ReadOnlyModelViewSet): queryset = EventVenue.objects.prefetch_related("eventblock_set__eventslot_set__event").annotate( num_slots=models.Count( "eventblock__eventslot", filter=Q(eventblock__eventslot__event=None) ) ).filter(num_slots__gt=0)
排查发现:移除prefetch_related("eventblock_set__eventslot_set__event")后,即使保留日期过滤响应也正常,但业务上需要这个预加载来返回关联对象,陷入两难。
解决思路
1. 拆分查询逻辑:先筛选再预加载
将注解筛选和关联预加载拆分为两步执行,避免两者逻辑冲突导致的性能损耗:
class EventVenueViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): # 第一步:筛选符合条件的场地 filtered_venues = EventVenue.objects.annotate( num_slots=models.Count( "eventblock__eventslot", filter=Q(eventblock__eventslot__event=None) & Q( eventblock__eventslot__start_time__gt=timezone.now() + timedelta(days=2) ) ) ).filter(num_slots__gt=0) # 第二步:对筛选结果执行预加载 return filtered_venues.prefetch_related("eventblock_set__eventslot_set__event")
2. 优化数据库索引
给eventslot表的start_time、event_id、eventblock_id字段建立联合索引,让日期过滤的查询更快命中数据,减少全表扫描的开销。
3. 自定义Prefetch对象,缩小预加载范围
不要预加载所有关联数据,而是用Prefetch对象给预加载的查询集加上过滤条件,只加载业务需要的关联数据:
from django.db.models import Prefetch class EventVenueViewSet(viewsets.ReadOnlyModelViewSet): def get_queryset(self): future_time = timezone.now() + timedelta(days=2) # 预加载符合日期条件的EventSlot及关联Event filtered_slots = EventSlot.objects.filter( event=None, start_time__gt=future_time ).select_related("event") # 预加载包含筛选后EventSlot的EventBlock filtered_blocks = EventBlock.objects.prefetch_related( Prefetch("eventslot_set", queryset=filtered_slots) ) # 先筛选场地,再预加载过滤后的关联数据 return EventVenue.objects.annotate( num_slots=models.Count( "eventblock__eventslot", filter=Q(eventblock__eventslot__event=None) & Q( eventblock__eventslot__start_time__gt=future_time ) ) ).filter(num_slots__gt=0).prefetch_related( Prefetch("eventblock_set", queryset=filtered_blocks) )
4. 检查SQL执行计划
通过print(queryset.query)输出实际执行的SQL语句,查看是否存在不合理的JOIN顺序或全表扫描,针对性调整查询结构。
内容的提问来源于stack exchange,提问作者bodger
相关产品推荐
相关产品推荐

