Django(MySQL)中避免循环查询高效重排模型对象的方法
优化方案:消除重排序接口的循环数据库查询
完全可以把逐次单条查询的逻辑改成批量查询,把N次数据库请求压缩到2次,适配你使用的MySQL环境。
你当前实现的核心性能问题是循环内调用Module.objects.get(id=module_id),传入N个模块ID就会产生N次单独的SELECT查询,属于典型的N+1查询问题,模块数量越多性能损耗越大。
具体实现思路
- 第一步从请求参数中提取所有需要调整排序的模块ID,统一做类型转换,兼容前端传字符串ID的场景
- 第二步用
id__in过滤条件,一次性查询出所有目标模块对象,把原来的N次查询合并成1次 - 可选增加参数合法性校验:如果传入的ID有对应不存在的模块,直接返回错误提示,避免静默失败
- 遍历批量查出的模块对象,匹配对应ID设置排序值,最后用
bulk_update完成批量更新,这一步本身就只会生成1条批量更新SQL,不会产生额外查询
优化后代码
from rest_framework.response import Response from rest_framework.decorators import action from rest_framework import status @action(detail=False, permission_classes=[], methods=["PUT"]) def reorder_modules(self, request, *args, **kwargs): """ ReOrdering Modules 入参示例 { "modules": { <module_id>: <ordering_number>, 12: 2, 13: 1, 14: 3 } } """ module_order_map = request.data.get("modules", {}) if not module_order_map: return Response({"detail": "缺少模块排序参数"}, status=status.HTTP_400_BAD_REQUEST) # 提取所有目标模块ID,统一转int,兼容字符串类型的ID传参 target_ids = [int(mid) for mid in module_order_map.keys()] # 单次查询捞出所有目标模块,替代循环单条get module_list = list(Module.objects.filter(id__in=target_ids)) # 校验非法ID if len(module_list) != len(target_ids): exist_id_set = {m.id for m in module_list} invalid_ids = [mid for mid in target_ids if mid not in exist_id_set] return Response( {"detail": f"模块ID不存在: {invalid_ids}"}, status=status.HTTP_400_BAD_REQUEST ) # 批量赋值排序字段 for mod in module_list: # 兼容key为字符串/数字两种传参格式 mod.order = module_order_map.get(str(mod.id), module_order_map.get(mod.id)) Module.objects.bulk_update(module_list, ["order"]) return Response({"detail": "successfully reordered course modules"}, status=status.HTTP_200_OK)
额外说明
- 你原示例里的请求体有笔误:
14, 3应该是14: 3,实际开发时要注意和前端约定好参数格式 - Django的
bulk_update在MySQL环境下可以正常工作,Django 2.2及以上版本都支持,更新100条数据的场景下,优化后数据库交互次数从原来的101次降到2次,性能提升非常明显
内容的提问来源于stack exchange,提问作者Shreehari Vaasistha L
相关产品推荐
相关产品推荐

