如何在Django递归生成嵌套菜单时避免重复SQL查询?
解决Django嵌套菜单重复SQL查询的优化方案
1. 一次性加载全量菜单数据,内存中构建树形结构
递归查询数据库是重复SQL的核心诱因,应先拉取所有菜单数据到内存,再用Python构建父子关系映射,彻底避免每次递归触发数据库请求。
修改MenuView视图
from django.views.generic import TemplateView from .models import Menu class MenuView(TemplateView): template_name = 'menu.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 一次性加载所有菜单,用select_related预加载parent外键关联数据 all_menus = Menu.objects.all().select_related('parent').order_by('parent_id', 'order') # 构建父节点ID到子菜单列表的映射字典 menu_tree = {} for menu in all_menus: parent_key = menu.parent_id if menu.parent_id is not None else 0 if parent_key not in menu_tree: menu_tree[parent_key] = [] menu_tree[parent_key].append(menu) context['menu_tree'] = menu_tree return context
2. 调整模板标签,基于内存树递归渲染
移除模板标签中的数据库查询逻辑,直接从内存映射中获取子菜单:
from django import template register = template.Library() @register.inclusion_tag('menu_item.html') def draw_menu(menu_tree, parent_key=0): # 从内存字典中直接取当前父节点的子菜单 children = menu_tree.get(parent_key, []) return {'children': children, 'menu_tree': menu_tree}
3. 适配模板渲染逻辑
子菜单模板(menu_item.html)
<ul> {% for item in children %} <li> <a href="{{ item.url }}">{{ item.name }}</a> <!-- 递归渲染子菜单,直接传入内存树结构 --> {% if item.id in menu_tree %} {% draw_menu menu_tree parent_key=item.id %} {% endif %} </li> {% endfor %} </ul>
主模板(menu.html)
{% load menu_tags %} <!-- 渲染根菜单(parent_key设为0,对应模型中parent为null的根节点) --> {% draw_menu menu_tree %}
4. 模型层规范优化(可选)
确保Menu模型的外键关联定义清晰,方便后续扩展:
from django.db import models class Menu(models.Model): name = models.CharField(max_length=100) url = models.CharField(max_length=200) parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, related_name='children') order = models.IntegerField(default=0) def __str__(self): return self.name
核心优化逻辑
通过select_related预加载外键关联数据,一次性拉取所有菜单记录;再用Python构建父子节点的内存映射,后续递归渲染时直接从字典中读取数据,彻底消除重复SQL查询。
内容的提问来源于stack exchange,提问作者Daniil Filippov
相关产品推荐
相关产品推荐

