Django内存泄漏排查:QuerySet未评估导致的内存飙升与优化方案

不晚
不晚
发布时间:2026-10-14| 2人浏览过
原创

本文记录了一次Django项目内存耗尽的排查过程。通过对比频繁调用接口与复杂汇总接口,定位到问题根源在于对QuerySet使用if判断导致全量数据加载至内存。文章通过代码示例复现了该问题,并引用Django官方文档推荐改用exists()方法,以最小化数据库查询开销并避免内存溢出,同时强调了大数据量测试的重要性。

Django内存异常排查背景

起因

Django作为Python领域知名的Web框架,被广泛应用于各类项目中。近期,笔者负责的Django部署服务器出现了内存异常现象:程序运行一段时间后,内存占用率持续攀升直至耗尽服务器资源。鉴于此前曾处理过类似的Python内存问题,笔者并未感到慌乱,并参考了之前的解决经验:https://www.jb51.net/article/151604.htm

然而,本次问题的排查难度远超预期。尝试沿用之前的tracemalloc库进行排查时,面临一个现实困境:项目包含近百个接口,逐一测试排查耗时过长,而线上故障处理时间紧迫。与上次简单项目相比,本次定位难度显著增加,亟需更高效的排查策略。

排查思路与过程

处理过程

Python项目中内存问题通常由代码逻辑缺陷引起。针对Web接口类项目,笔者制定了以下排查思路:

  1. 优先排查调用频率较高的接口
  2. 其次排查数据聚合类接口(涉及复杂查询)
  3. 若上述步骤未发现问题,再排查其余接口

实际排查过程大致遵循此逻辑。在对高频调用接口的检查中,未发现内存异常;内存飙升现象最终出现在数据汇总相关接口上。

此类接口对初级开发者而言风险较高。由于查询逻辑复杂,若编码基础薄弱,极易在此类接口中引入Bug。

问题定位与代码复现

最终定位显示,问题源于Django ORM的不当使用。以下通过简化代码实例进行说明:


class Student(models.Model):
 name = models.CharField(max_length=20)
 name2 = models.CharField(max_length=20)
 name3 = models.CharField(max_length=20)
 name4 = models.CharField(max_length=20)
 name5 = models.CharField(max_length=20)
 name6 = models.CharField(max_length=20)
 name7 = models.CharField(max_length=20)
 name8 = models.CharField(max_length=20)
 name9 = models.CharField(max_length=20)
 name10 = models.CharField(max_length=20)
 name11 = models.CharField(max_length=20)
 name12 = models.CharField(max_length=20)
 name13 = models.CharField(max_length=20)
 name14 = models.CharField(max_length=20)
 name15 = models.CharField(max_length=20)
 age = models.IntegerField(default=0)

实际表结构字段较多,此处通过多个name字段模拟,问题代码涉及该表的接口逻辑:


def index(request):
 studets = Student.objects.filter(age__gt=20)
 if studets:
  pass
 return HttpResponse("test memory")

为便于复现内存问题,通过脚本向Student模型插入20000条数据。数据量越大,问题表现越明显。

使用测试脚本对该接口进行并发请求并观察内存状态,可见内存瞬间飙升。若数据量更大、请求更频繁,内存占用将在一段时间内居高不下并持续上涨。问题根源何在?

关键在于代码中的if判断逻辑。filter查询返回的是QuerySet对象,当过滤后的数据量巨大时,直接通过if进行布尔判断会触发QuerySet的全量评估(Evaluation),导致所有数据加载至内存,从而引发内存占用过高及响应延迟。数据量越大,内存占用越显著。

Django官方文档对此有明确说明:

exists()¶ Returns True if the QuerySet contains any results, and False if not. This tries to perform the query in the simplest and fastest way possible, but it does execute nearly the same query as a normal QuerySet query.

exists() is useful for searches relating to both object membership in a QuerySet and to the existence of any objects in a QuerySet, particularly in the context of a large QuerySet.

The most efficient method of finding whether a model with a unique field (e.g. primary_key) is a member of a QuerySet is:


entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
 print("Entry contained in queryset")

Which will be faster than the following which requires evaluating and iterating through the entire queryset:


if entry in some_queryset:
 print("Entry contained in QuerySet")

And to find whether a queryset contains any items:


if some_queryset.exists():
 print("There is at least one object in some_queryset")

Which will be faster than:


if some_queryset:
 print("There is at least one object in some_queryset")

… but not by a large degree (hence needing a large queryset for efficiency gains).

Additionally, if a some_queryset has not yet been evaluated, but you know that it will be at some point, then using some_queryset.exists() will do more overall work (one query for the existence check plus an extra one to later retrieve the results) than using bool(some_queryset), which retrieves the results and then checks if any were returned.

因此,将代码中的if判断修改为if not studets.exists()即可解决该内存问题。

这是一个细微的知识盲点,但若使用不当,可能导致严重的内存溢出事故。

总结与建议

总结

除单元测试外,必须执行大数据量测试。若本次在测试阶段进行了足够数据量的验证,该问题可被更早发现。

问题

需更深入熟悉基础库的使用规范

排查思路需清晰明确,否则易陷入无从下手的困境

延伸阅读

  • https://docs.djangoproject.com/en/3.0/ref/models/querysets/
  • https://www.jb51.net/article/151604.htm

到此这篇关于django内存异常排查及解决方法的文章就介绍到这了,更多相关django内存异常排查内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。