Django多表关联查询仅执行单条SQL的问题求助
解决Django多表关联查询N+1问题的方案
针对你的情况,核心问题是未正确覆盖所有关联关系导致N+1查询,以及使用raw查询时错误访问模型关联属性触发额外查询,以下是具体解决方案:
一、优化ORM查询(推荐)
你的select_related缺少了嵌套关联和部分关联,导致访问os等关联数据时触发大量额外查询。假设你的模型关系如下:
SinfraHosts关联Domain(字段名domain)SinfraHosts关联HostsFixDataScans(字段名last_fix_data_scan)HostsFixDataScans关联Os(字段名os)SinfraHosts关联CompaniesDepartments(字段名system_care_by_department、additional_software_care_by_department)SinfraHosts关联Companies(字段名installation_by_company、care_by_company)
优化后的查询代码:
machines = SinfraHosts.objects.filter(offline=0) \ .select_related( 'domain', 'last_fix_data_scan__os', # 嵌套关联os表,避免单独查询 'installation_by_company', 'system_care_by_department', 'care_by_company', 'additional_software_care_by_department' # 补充缺失的关联 ) \ .order_by('system_care_by_department__department_name') # 对应SQL的排序字段
关键说明:
select_related('last_fix_data_scan__os')会一次性JOINhosts_fix_data_scans和os表,避免访问os数据时触发单独查询- 补充
additional_software_care_by_department关联,覆盖SQL中对应的JOIN - 排序字段需对应模型的关联字段路径,而非SQL别名
二、使用values()直接返回字典(适合无需模型实例的场景)
如果不需要完整的模型实例,使用values()可以直接生成包含所有所需字段的单条SQL,返回字典列表,彻底避免N+1问题:
machines = SinfraHosts.objects.filter(offline=0) \ .values( 'id', 'hostname', 'description', 'installation_by_company_id', 'care_by_company_id', 'system_care_by_department_id', 'system_care_accounting_type_id', 'domain__domain_name', 'last_fix_data_scan__os__operatingsystem', 'last_fix_data_scan__os__lsbdistcodename', 'last_fix_data_scan__os__lsbdistrelease', 'last_fix_data_scan__os__lsbmajdistrelease', 'system_care_by_department__department_name', 'additional_software_care_by_department__department_name', 'installation_by_company__company_name', 'installation_by_company__company_abrebreation' ) \ .order_by('system_care_by_department__department_name')
三、修复raw查询的额外查询问题
使用raw()时,若访问模型的关联属性(如machine.system_care_by_department),Django会自动触发额外查询。解决方法是:
- 移除原始SQL中多余的
JOIN companies_departments(重复JOIN会导致性能问题) - 直接通过raw实例的别名字段访问数据,不要触发模型关联
修正后的raw查询代码:
sql = """ SELECT `hosts`.`id`, `hosts`.`hostname`, `domains`.`domain_name`, `hosts`.`description`, `hosts`.`installation_by_company_id`, `hosts`.`care_by_company_id`, `hosts`.`system_care_by_department_id`, `hosts`.`system_care_accounting_type_id`, `os`.`operatingsystem`, `os`.`lsbdistcodename`, `os`.`lsbdistrelease`, `os`.`lsbmajdistrelease`, `system_care_department`.`department_name` AS `system_care_department`, `additional_software_care_department`.`department_name` AS `additional_software_care_department`, `system_installation_company`.`company_name` AS `system_installation_company`, `system_installation_company`.`company_abrebreation` AS `system_installation_company_abrebreation` FROM `hosts` JOIN `domains` ON `hosts`.`domain_id` = `domains`.`id` JOIN `hosts_fix_data_scans` ON `hosts_fix_data_scans`.`host_id` = `hosts`.`id` JOIN `os` ON `os`.`id` = `hosts_fix_data_scans`.`os_id` JOIN `companies_departments` AS `system_care_department` ON `system_care_department`.`id` = `hosts`.`system_care_by_department_id` JOIN `companies_departments` AS `additional_software_care_department` ON `additional_software_care_department`.`id` = `hosts`.`additional_software_care_by_department_id` JOIN `companies` AS `system_installation_company` ON `system_installation_company`.id = `hosts`.`installation_by_company_id` WHERE `hosts`.`offline` = 0 ORDER BY `system_care_department`; """ machines = SinfraHosts.objects.raw(sql) # 访问示例:直接使用别名字段 for machine in machines: print(machine.system_care_department) # 不要用machine.system_care_by_department.department_name
内容的提问来源于stack exchange,提问作者Henri Schomäcker
相关产品推荐
相关产品推荐

