从Django 1.8迁移到3.2后数据库查询速度骤降问题
Django 1.8迁移到3.2后查询性能骤降问题排查与解决建议
问题背景
将项目从Django 1.8迁移到3.2后,相同数据库环境下,报表生成函数速度下降4倍(原耗时8秒,现耗时30-40秒)。数据库使用MySQL 5.7,更换为8.0.32版本后性能无改善。
核心查询代码
复杂查询:
qs = PartitaDanno.objects.filter(incarico__cliente_intestatario_fattura_id=8006 ).select_related('incarico', 'anagrafica_assctp', 'incarico__naturaincarico' ).prefetch_related('rate' ).order_by('-incarico__anno', '-incarico__numero', 'pk')
简单查询(遍历仍需20秒):
qs = PartitaDanno.objects.filter(incarico__cliente_intestatario_fattura_id=8006)
关键数据
- PartitaDanno表共16000行,模型包含215个字段
- 查询仅返回1700行结果
- 两个Django版本生成的原生SQL完全相同
- 原生SQL在phpMyAdmin中执行仅需3.5秒
- queryset.explain()结果显示SQL执行本身耗时较低,主要瓶颈在遍历查询集阶段
原生SQL与执行计划
原生SQL:
SELECT `sinistri_partitadanno`.`id`, ...(省略部分字段) ORDER BY `sinistri_incarico`.`anno` DESC, `sinistri_incarico`.`numero` DESC, `sinistri_partitadanno`.`id` ASC
执行计划:
-> Sort row IDs: sinistri_incarico.anno DESC, sinistri_incarico.numero DESC, sinistri_partitadanno.id (actual time=163.955..172.010 rows=1761 loops=1) -> Table scan on <temporary> (cost=2518.91..2544.83 rows=1874) (actual time=153.040..163.135 rows=1761 loops=1) -> Temporary table (cost=2518.90..2518.90 rows=1874) (actual time=153.007..153.007 rows=1761 loops=1) -> Nested loop left join (cost=2331.46 rows=1874) (actual time=0.247..62.163 rows=1761 loops=1) -> Nested loop inner join (cost=1675.43 rows=1874) (actual time=0.224..52.547 rows=1761 loops=1) -> Nested loop left join (cost=1019.40 rows=1677) (actual time=0.089..17.068 rows=1677 loops=1) -> Index lookup on sinistri_incarico using b842d1d4b6d5fa98d8dc06d2c92e02c5 (cliente_intestatario_fattura_id=8006) (cost=432.45 rows=1677) (actual time=0.081..15.586 rows=1677 loops=1) -> Single-row index lookup on sinistri_naturaincarico using PRIMARY (id=sinistri_incarico.naturaincarico_id) (cost=0.25 rows=1) (actual time=0.001..0.001 rows=1 loops=1677) -> Index lookup on sinistri_partitadanno using sinistri_pa_incarico_id_398e55f0a3d8c2c0_fk_sinistri_incarico_id (incarico_id=sinistri_incarico.id) (cost=0.28 rows=1) (actual time=0.018..0.021 rows=1 loops=1677) -> Single-row index lookup on T5 using PRIMARY (id=sinistri_partitadanno.anagrafica_assctp_id) (cost=0.25 rows=1) (actual time=0.005..0.005 rows=1 loops=1761)
解决方案建议
减少模型实例化开销:PartitaDanno有215个字段,Django 3.2实例化模型对象的开销远高于1.8。使用
only()指定报表所需的字段,避免加载冗余字段:qs = PartitaDanno.objects.filter(incarico__cliente_intestatario_fattura_id=8006 ).only('id', '所需字段1', '所需字段2' ).select_related('incarico', 'anagrafica_assctp', 'incarico__naturaincarico' ).prefetch_related('rate' ).order_by('-incarico__anno', '-incarico__numero', 'pk')也可使用
defer()排除不需要的大字段或计算密集型字段。使用iterator()优化遍历:结合
iterator()分批加载数据,避免一次性将所有实例存入内存,降低内存占用与遍历耗时。注意prefetch_related会在iterator()执行前完成预取,不会重复查询:for obj in qs.iterator(): # 报表处理逻辑优化数据库连接配置:调整Django数据库连接参数,匹配MySQL的优化设置,例如:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'your_db', 'USER': 'your_user', 'PASSWORD': 'your_pass', 'HOST': 'your_host', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES', innodb_buffer_pool_size=256M", 'charset': 'utf8mb4', }, 'CONN_MAX_AGE': 60, } }重点确保
innodb_buffer_pool_size足够,提升数据库缓存效率。替换模型实例为字典/元组:如果报表生成不需要完整模型对象,使用
values()或values_list()直接获取数据,跳过模型实例化步骤:qs = PartitaDanno.objects.filter(...).values('id', 'incarico__anno', 'incarico__numero')优化排序索引:创建联合索引
sinistri_incarico(cliente_intestatario_fattura_id, anno DESC, numero DESC),让数据库直接返回排序后的结果,减少临时表排序的开销,间接降低Django后续处理的时间。
内容的提问来源于stack exchange,提问作者MtSorre
相关产品推荐
相关产品推荐

