flask应用退出时数据库连接未释放会导致进程僵死,正确做法是用teardown_appcontext绑定g.db实现请求级自动清理,生产环境需配带健康检查的连接池并设超时。

Flask 应用退出时数据库连接没释放,导致进程僵死
Flask 本身不管理数据库连接生命周期,app.run() 启动的开发服务器在 Ctrl+C 退出时,若连接未显式关闭,底层 socket 可能滞留在 TIME_WAIT 或直接卡住,表现为进程无法彻底退出、端口被占、日志停在 “Shutting down…”。这不是 Flask Bug,而是资源未归还的典型表现。
- 使用
sqlite3时,不调用connection.close()可能导致 .db 文件被锁,后续启动报database is locked - 使用
psycopg2或pymysql时,连接池未清理会堆积空闲连接,PostgreSQL/pgbouncer 可能拒绝新连接 -
atexit.register()不可靠——它不触发于 SIGKILL、gunicorn worker 强杀、Docker stop 等场景
用 teardown_appcontext 处理请求级连接释放
这是最常用也最安全的方案:每个请求结束时自动清理当前上下文绑定的连接。它不依赖进程退出,而是绑定到 Flask 的应用上下文生命周期,适用于所有部署方式(dev server / gunicorn / uwsgi)。
- 必须配合
g.db = get_db()这类绑定到g对象的模式,否则无法识别“当前连接” - 仅对通过
app.app_context()或请求上下文建立的连接生效;后台线程或定时任务中手动创建的连接需单独处理 - 不要在
teardown_appcontext中抛异常,否则会掩盖原始错误,建议用try/except包裹close()
@app.teardown_appcontext
def close_db(error):
if hasattr(g, 'db'):
try:
g.db.close()
except Exception:
pass # 忽略 close 失败,避免干扰 teardown 流程
生产环境必须配连接池并设超时
单连接 + teardown_appcontext 在高并发下会成为瓶颈,且无法应对连接意外中断。真正的“优雅关闭”依赖连接池自身的健康检查与回收机制。
- SQLAlchemy 用户:启用
pool_pre_ping=True和pool_recycle=3600,让连接在使用前探测有效性,并定期强制刷新 - psycopg2 用户:用
pgbouncer代理层比直连更可控;若直连,设置options='-c statement_timeout=30000'防查询挂死 - MySQL 用户:确保
wait_timeout和interactive_timeout与应用层pool_recycle对齐,否则池内连接会被服务端静默断开
信号处理只用于主进程收尾,别指望它兜底
对 gunicorn/uwsgi 主进程或 systemd service,可监听 SIGTERM 做最后清理,但它不是替代 teardown_appcontext 的方案,而是补充。
- Flask 开发服务器不转发
SIGTERM给应用,所以该逻辑在app.run()下不会执行 - gunicorn 的
--preload模式下,init阶段加载的模块可能早于 worker fork,此时注册的信号处理器只在 master 进程生效 - 真正该在这里做的只有:关闭全局连接池、取消后台定时任务、写 shutdown 日志——而不是逐个关请求级连接(它们早已由
teardown_appcontext清理)
复杂点在于:连接是否真的“属于当前进程”很难静态判断。比如用 multiprocessing 启的子进程持有独立 DB 连接,父进程的信号处理器根本看不到它。这种场景只能靠子进程自己监听信号,或改用线程 + 共享连接池。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











