如何优化Django Rest Framework序列化器查询以提升响应速度
你的代码主要问题是循环执行数据库查询(N+1问题)、冗余的关联数据查询、低效的模糊查询这几点导致响应慢,下面是针对性的优化方案:
一、优化数据库查询次数与关联预取
1. 预取Search模型的关联数据
原代码中obj.search_tags.select_related('component')是多余的(因为search的component就是当前obj),换成预取channel和channel__category,避免后续查询YTVideo时触发额外的DB请求:
searches = obj.search_tags.filter(is_active=True).select_related('channel', 'channel__category')
2. 去掉冗余的select_related
原循环中videos.select_related('channel')是重复操作,前面查询时已经加过select_related('channel'),直接移除即可。
二、优化查询条件与模糊查询效率
1. 提前处理标签并过滤空值
避免在查询时重复拆分字符串,提前处理标签并过滤空值,减少无效查询:
tags = [tag.strip() for tag in search.content.split(',') if tag.strip()] if not tags: continue # 无有效标签时跳过查询
2. 用外键替代名称查询(如果适用)
如果Search模型的category是外键关联Category模型,把Q(channel__category__name=search.category)改成Q(channel__category=search.category),外键查询依赖索引,比字符串匹配快很多。
3. 替换低效的icontains为全文搜索
title__icontains是无索引的模糊查询,数据量大时极慢。如果用PostgreSQL,改用全文搜索提升速度:
from django.contrib.postgres.search import SearchVector, SearchQuery # 构建全文搜索查询 search_query = SearchQuery(' | '.join(tags), search_type='websearch') videos = YTVideo.objects.select_related('channel').annotate( search=SearchVector('title') ).filter( (Q(channel=search.channel) | Q(channel__category=search.category)) & Q(search=search_query) ).order_by(search.ordering_field)[:search.limit]
同时给YTVideo的title字段加GIN索引:
class YTVideo(models.Model): # ... 其他字段 title = models.CharField(max_length=255) class Meta: indexes = [ models.Index(fields=['title'], name='ytvideo_title_idx'), # 全文搜索专用索引(可选) models.Index(fields=['search'], name='ytvideo_search_idx', opclasses=['gin']) ]
三、避免修改数据库对象,优化序列化逻辑
原代码中动态给video.thumbnail赋值会修改DB对象,容易引发意外。改用序列化器的context传递缩略图:
1. 修改YTVideoSerializer
class YTVideoSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() class Meta: model = YTVideo fields = ['id', 'title', 'channel', 'thumbnail', ...] # 你的其他字段 def get_thumbnail(self, obj): return self.context.get('thumbnail')
2. 在get_videos中传递context
serializer = YTVideoSerializer(videos, many=True, context={'thumbnail': thumbnail}) all_videos_data.extend(serializer.data)
四、进阶优化:缓存与索引
1. 缓存查询结果
如果视频数据不是实时更新,用Django缓存缓存结果,避免重复查询:
from django.core.cache import cache def get_videos(self, obj): cache_key = f'component_videos_{obj.pk}' cached_data = cache.get(cache_key) if cached_data: return cached_data # ... 上面的查询与序列化逻辑 # 缓存1小时(可根据需求调整) cache.set(cache_key, all_videos_data, timeout=3600) return all_videos_data
2. 给排序字段加索引
确保order_by使用的字段(比如search.ordering_field对应的YTVideo字段)有数据库索引,避免排序时全表扫描:
class YTVideo(models.Model): # 假设排序字段是publish_date publish_date = models.DateTimeField(db_index=True)
优化后的完整get_videos示例:
from django.core.cache import cache from django.db.models import Q from django.contrib.postgres.search import SearchVector, SearchQuery import operator def get_videos(self, obj): cache_key = f'component_videos_{obj.pk}' cached_data = cache.get(cache_key) if cached_data: return cached_data all_videos_data = [] # 预取关联数据,避免N+1 searches = obj.search_tags.filter(is_active=True).select_related('channel', 'channel__category') for search in searches: thumbnail = search.thumbnail.url if search.thumbnail else None # 预处理标签 tags = [tag.strip() for tag in search.content.split(',') if tag.strip()] if not tags: continue # 构建查询条件 channel_q = Q(channel=search.channel) category_q = Q(channel__category=search.category) # 用外键而非名称 # 全文搜索替代icontains search_query = SearchQuery(' | '.join(tags), search_type='websearch') videos = YTVideo.objects.select_related('channel').annotate( search=SearchVector('title') ).filter( (channel_q | category_q) & Q(search=search_query) ).order_by(search.ordering_field)[:search.limit] # 序列化并传递缩略图 serializer = YTVideoSerializer(videos, many=True, context={'thumbnail': thumbnail}) all_videos_data.extend(serializer.data) cache.set(cache_key, all_videos_data, timeout=3600) return all_videos_data
内容的提问来源于stack exchange,提问作者afi

