90%的连接数耗尽问题源于Django未管好连接生命周期,应设CONN_MAX_AGE=0为安全起点;Celery任务、管理命令及原生cursor易泄漏连接,需显式关闭。

直接结论:90% 的连接数耗尽问题,不是数据库不够用,而是 Django 自己没管好连接生命周期——先关掉 CONN_MAX_AGE,再检查 Celery、管理命令和原生 cursor 是否漏关连接。
CONN_MAX_AGE 设为 0 是最安全的起点
Django 默认不复用连接(短连接),但一旦设了 CONN_MAX_AGE > 0,就要求 MySQL 服务端 wait_timeout 必须大于它,否则连接会被 server 主动断开,引发 Lost connection。生产环境多数情况没必要复用连接,尤其当你的部署模型是 gunicorn/uWSGI 多进程 + Nginx 时,每个 worker 进程自己维护连接更可控。
-
CONN_MAX_AGE = 0表示每次请求结束立即关闭连接,杜绝跨请求残留 - 若真要设正整数(比如
60),必须确认 MySQL 的wait_timeout≥ 65(留 5 秒缓冲),且不能在负载均衡后端混用 sticky session -
OPTIONS里加'init_command': 'SET SESSION wait_timeout=28800'是临时补丁,不解决根本问题,还可能干扰事务隔离
Celery 和自定义命令是最常见的泄漏源头
HTTP 请求路径下,Django 中间件会自动调用 connection.close();但 Celery 任务、python manage.py xxx 这类非 HTTP 场景完全绕过这套机制,连接一旦打开就一直挂着。
- Celery 任务里用了
Model.objects.filter(...)却没触发执行(比如没接.list()、.first()或.count()),连接不会建立;但只要一触发,就可能卡住不释放 - 循环处理大批数据时,每轮都新建 ORM 查询却不显式关闭:应在循环外或每批后调用
from django.db import connection; connection.close() - 使用
transaction.atomic()包裹的代码块,如果中途异常退出,process_response不执行,连接也不会归还——必须确保有finally或上下文管理器兜底
原生 cursor 比 ORM 更容易出问题
ORM 隐藏了连接细节,但手写 connection.cursor() 就等于直面底层资源管理。漏掉任意一环,连接就永远留在 SHOW PROCESSLIST 里。
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
立即学习“Python免费学习笔记(深入)”;
- 必须成对调用:
cursor = connection.cursor()→cursor.execute(...)→cursor.close()→connection.close() - 别依赖
__del__或垃圾回收——Python 的 GC 不保证及时性,尤其在长生命周期进程(如 Celery worker)中 - 用上下文管理器更可靠:
with connection.cursor() as cursor:可自动 close cursor,但connection仍需手动关(Django 不自动管理 connection 级关闭)
要不要上第三方连接池?先回答这三个问题
引入 django-db-geventpool 或 SQLAlchemy + pgbouncer 类方案,代价不小,且常和 Django 原生机制冲突。
- 你是否已确认瓶颈真是“连接建立开销”?还是只是连接泄漏未清理?用
SELECT COUNT(*) FROM information_schema.PROCESSLIST;查实时连接数,比盲加池子更有意义 - 是否已在 gunicorn + gevent 模式下运行?第三方池基本只在协程模型里生效;纯 sync worker 下加池等于给单线程配多把锁
- 是否已 patch
psycopg2或pymysql?没做gevent.monkey.patch_psycopg()就跑连接池,所有 I/O 依然阻塞
真正难排查的,是那些“看起来没操作 DB 却悄悄建了连接”的地方:比如某个中间件里调了 User.objects.get() 但被 try/except 吞掉异常,process_response 跳过,连接就悬在那里。这类问题不会报错,只会让连接数缓慢爬升,直到某天凌晨 MySQL 直接拒绝新连接。

















