如何优化面向React前端数据表页面的Django API响应性能?
你这段代码8条数据耗时超过3秒,核心问题基本都是循环遍历触发N+1数据库查询+不必要的模型实例化/计算开销,按优先级落地以下优化即可,改完后8条数据的响应耗时通常能降到20ms以内:
优先解决外键关联的N+1查询问题
你在循环中访问dep.PLATFORM_TYPE.VALUE和dep.DEPLOYMENT_PLATFORM.VALUE时,每遍历一条记录都会单独发起一次SQL查询关联表,8条记录仅这两处就会产生16次额外的数据库往返,这是最主要的耗时来源。
用select_related在初始查询时就通过JOIN把关联表数据一次性查出来,后续访问关联对象不会再触发额外查询:deployments = deployment.objects.filter( LAUNCH_DATE__isnull=False, HISTORICAL=False ).select_related("PLATFORM_TYPE", "DEPLOYMENT_PLATFORM")排查模型属性的隐式查询,把计算逻辑下沉到数据库层
你代码里用到的status、last_report、next_report、days_since_last、last_cycle、age如果是模型上用@property定义的计算属性,先检查这些属性内部有没有数据库查询逻辑——如果有,不要在Python循环里逐行计算,用annotate把计算逻辑放到数据库侧一次性完成。
比如age是部署时间到当前时间的天数,完全可以在查询时直接算好:from django.db.models import F, ExpressionWrapper, DurationField from django.utils import timezone deployments = deployments.annotate( age_cal=ExpressionWrapper( timezone.now() - F("LAUNCH_DATE"), output_field=DurationField() ) ) # 后续取值直接拿dep.age_cal.days即可,不用逐行触发Python侧的计算逻辑用
values()直接取需要的字段,省掉模型实例化开销
你当前的查询会把deployment表所有字段都加载成模型实例,很多不需要的字段会浪费内存和查询时间。直接用values()指定需要返回的字段,连逐行拼字典的步骤都能省大半:from django.db.models.functions import DateFormat, DateTimeFormat deployments = deployment.objects.filter( LAUNCH_DATE__isnull=False, HISTORICAL=False ).select_related("PLATFORM_TYPE", "DEPLOYMENT_PLATFORM").annotate( # 直接在数据库层完成日期格式化,省掉Python层strftime的开销 LAUNCH_DATE=DateFormat("LAUNCH_DATE", "%Y-%m-%d"), last_report=DateTimeFormat("last_report", "%Y-%m-%d %H:%M"), next_report=DateTimeFormat("next_report", "%Y-%m-%d %H:%M"), age_cal=ExpressionWrapper(timezone.now() - F("LAUNCH_DATE"), output_field=DurationField()), # 直接取关联表的字段值,不用后续再从关联对象里取 PLATFORM_TYPE=F("PLATFORM_TYPE__VALUE"), DEPLOYMENT_PLATFORM=F("DEPLOYMENT_PLATFORM__VALUE") ).values( "FLOAT_SERIAL_NO", "PLATFORM_NUMBER", "PLATFORM_TYPE", "DEPLOYMENT_CRUISE_ID", "DEPLOYMENT_PLATFORM", "LAUNCH_DATE", "status", "last_report", "next_report", "days_since_last", "last_cycle", age=F("age_cal") ) # 最后只需要处理age的天数转换,几乎没有额外开销 res = [] for dep in deployments: dep["age"] = dep["age"].days res.append(dep)给查询条件加联合索引
打开Django的数据库查询日志确认执行计划,给过滤用到的HISTORICAL和LAUNCH_DATE字段加联合索引,可以进一步降低数据库层的查询耗时:class deployment(models.Model): # 你的其他字段定义 class Meta: indexes = [ models.Index(fields=["HISTORICAL", "LAUNCH_DATE"]), ]加完后执行
python manage.py makemigrations和python manage.py migrate同步索引即可。
优化完成后可以用django-debug-toolbar确认实际执行的SQL数量,正常场景下这个接口只会触发1次数据库查询,不会再有额外的隐式查询。
内容的提问来源于stack exchange,提问作者R. Anderson

