Django长连接致内存持续上涨主因是CONN_MAX_AGE过大或为0时连接不释放,导致游标、结果集缓存及QuerySet未消费完而滞留内存。

为什么Django长连接会让内存持续上涨
Django默认使用数据库连接池(实际是连接复用机制),但CONN_MAX_AGE设得过大或为0(永久连接)时,连接不会主动关闭,而Python的DB-API驱动(如psycopg2或mysqlclient)在长时间持有连接期间,可能累积未释放的游标、结果集缓存、甚至底层C层的内存块。更关键的是,Django的QuerySet缓存和connection对象本身会绑定到线程/协程生命周期——在uWSGI/Gunicorn多进程+多线程模型下,一个worker里多个请求共享同一个connection,但每个请求产生的QuerySet若未被消费完(比如只取了前几条就中断),其结果缓冲区可能滞留内存。
检查是否真由连接引起内存增长
别急着调CONN_MAX_AGE,先确认问题根源。常见误判是把ORM缓存、第三方库(如celery任务中未清理的QuerySet)或日志输出导致的内存占用当成连接问题。
- 用
psutil.Process().memory_info().rss定期采样worker进程内存,并配合django.db.connections['default'].queries_log看查询积压量 - 在视图结尾加
from django.db import connection; connection.close()临时验证:如果加了之后内存稳定,说明连接复用确实没被正确管理 - 启用
psycopg2的trace模式(设置环境变量PGOPTIONS="-c log_statement=all")观察是否有大量DECLARE/FETCH未配对,这是游标泄漏的典型信号
调整CONN_MAX_AGE要避开的三个坑
CONN_MAX_AGE不是越大越好,也不是越小越安全。它控制的是单个数据库连接在连接池中复用的最长时间(单位:秒),但它的行为受部署方式强约束:
- 在Gunicorn的
syncworker中设CONN_MAX_AGE = 60有意义;但在gevent或eventlet异步worker中,由于协程切换频繁,连接可能被多个协程交叉使用,此时设非零值反而易触发DatabaseError: server closed the connection unexpectedly - 设为
0(禁用持久连接)看似“干净”,但会显著增加TCP握手和认证开销,尤其在高并发短请求场景下,CPU和数据库负载可能飙升 - 若用uWSGI,必须同时配置
max-requests(如max-requests=1000)并设CONN_MAX_AGE略小于该值,否则连接会在worker重启前一直挂着,无法释放
比调CONN_MAX_AGE更有效的三件事
真正压住内存增长的,往往不是连接时长,而是及时切断数据流和清理上下文:
立即学习“Python免费学习笔记(深入)”;
- 所有
QuerySet必须显式消费:避免qs = MyModel.objects.filter(...)后只用qs[0]或qs.exists()——这会让剩余结果留在内存缓冲区;改用.first()、.count()或list(qs)明确边界 - 在中间件或
request_finished信号里强制清理:from django.db import connection def close_connection(sender, **kwargs): if connection and not connection.closed: connection.close()然后用request_finished.connect(close_connection) - 对大表分页,禁用
QuerySet缓存:MyModel.objects.all().iterator(chunk_size=2000),配合yield逐批处理,不构建完整列表
连接本身只是容器,真正吃内存的是没被释放的结果集和缓存引用。只要每次请求结束前确保connection上没有活跃游标、QuerySet不跨请求存活,哪怕CONN_MAX_AGE=300也不会持续涨内存。


















