绝大多数情况是数据库连接池耗尽或线程/进程配置与业务模型错配:gunicorn多worker、pywebio单线程等同步框架下,worker数×线程数必须≤数据库连接池总大小,否则请求排队等待连接。

Python Web 应用在高并发下响应慢,**绝大多数情况不是代码写得差,而是数据库连接池耗尽或线程/进程配置与业务模型严重错配**。尤其在使用 Flask、FastAPI(同步模式)或 PyWebIO 这类默认单 Worker 的框架时,问题会立刻暴露。
数据库连接池打满:查 psycopg2.OperationalError: timeout expired 或 mysql.connector.errors.PoolError
连接池大小 ≠ 并发请求数,更不等于线程数。比如你开了 4 个线程,但数据库连接池只设了 2,那至少一半请求会在 getconn() 上卡住等待。
- PostgreSQL(psycopg2)默认不带连接池;
pool_size=5在中等负载下极易打满,建议按worker 数 × 2~3配置,上限不超过数据库 max_connections 的 70% MySQL(mysql-connector-python)的
pool_size 和 pool_reset_session 必须显式开启,否则复用连接可能残留事务状态SQLAlchemy 用户注意:create_engine(..., pool_size=10, max_overflow=20) 中的 max_overflow 是临时超限连接数,它不持久,用完即销毁,但会加剧连接风暴用 SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active'(PG)或 SHOW STATUS LIKE 'Threads_connected'(MySQL)实时看真实连接占用
Web 服务器线程数 vs 数据库连接池 vs 实际并发能力
三者必须对齐,否则必然出现“线程空转等连接”或“连接空闲等线程”。典型错误是:Gunicorn 开 8 个 sync worker,每个 worker 用 4 线程,却只配了 8 个数据库连接——实际最多只能跑 8 个 DB 请求,其余请求全在排队。
- 同步框架(如 Flask 默认 + WSGI):Worker 数 × 每个 Worker 的线程数 ≤ 数据库连接池总大小 FastAPI 同步端点同理;若混用
async def 端点,但底层 DB 驱动仍是同步的(如普通 sqlite3 或未换 asyncpg),异步层只是假并发,DB 仍串行阻塞PyWebIO 的 start_server() 默认单线程,threading 手动启任务能缓解 UI 卡顿,但不解决 DB 连接争抢——它仍共用同一个连接池实例检查 Gunicorn --workers 4 --threads 2 时,应用内是否对每个请求都新建 engine?必须全局复用一个 engine 实例,否则连接池失效
容易被忽略的“隐式连接泄漏”
不是没关连接,而是根本没机会关。常见于异常路径、生成器未完全消费、或上下文管理器被跳过。
- Flask-SQLAlchemy 的
db.session.remove()不是银弹——如果视图函数里抛了未捕获异常,session 可能滞留在 inactive 状态,连接不归还池 手动用
connection.close() 前,确认它真属于池管理的连接;直接调 engine.dispose() 会干掉整个池,引发雪崩PyWebIO 中用 file_upload() 后立刻做数据库写入?上传文件的 content 是 bytes,若直接塞进 ORM 字段又没设 LargeBinary,可能触发隐式长事务+连接占用用 tracemalloc + psutil.Process().memory_info().rss 对比“空载”和“压测后”内存增长,若持续上涨,大概率有连接或 session 对象被意外强引用
真正卡住你的,往往不是 SQL 多慢,而是第 97 个请求在等第 3 个连接释放——而那个连接早在 8 秒前就该还给池了,只是因为某个 try/except 漏掉了 finally 里的 close。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











