连接池耗尽时典型现象是大量asyncio.TimeoutError或TooManyConnectionsError、响应时间飙升、pool.acquire()超时;根本原因是max_size设置不当(多worker下需乘以worker数)或连接泄漏(未正确await/归还),而非单纯并发高。

连接池耗尽时的典型错误现象
服务突然大量返回 asyncio.TimeoutError 或 asyncpg.exceptions.TooManyConnectionsError,FastAPI 接口响应时间飙升,日志里反复出现 pool.acquire() timed out。这不是数据库挂了,而是连接池里没连接可分了——哪怕 max_size 设得很高,也可能卡在“等连接释放”这一步。
为什么 max_size 不能乱调高
连接池不是越大越好。PostgreSQL 的 max_connections 默认通常只有 100,而你开 4 个 Gunicorn worker,每个 worker 的 pool max_size=30,实际就占掉 120 个连接,直接触发数据库拒绝新连接。MySQL 同理,应用池总连接数别超过 max_connections 的 70%。
-
max_size是单个 pool 实例的上限,多 worker 场景下要乘以 worker 数 - 别按 CPU 核数设
max_size:Python 的 GIL 和 I/O 等待让真实并发远低于理论值 - 设太高会压垮数据库,设太低又容易耗尽;建议从
min_size=5、max_size=20起步,再根据压测中pg_stat_activity的active连接数调整
连接泄漏是耗尽的真正元凶
根本原因往往不是并发高,而是异步任务没正确 await 或忘了 close。比如同一个 AsyncSession 被多个协程共享,或 session.begin() 后没配 await session.commit() / await session.rollback(),连接就永远卡在“已分配未归还”状态。
- 用
async with pool.acquire() as conn:是安全写法,异常时也会自动归还 - 避免手动调用
pool.acquire()+pool.release(),漏掉release就泄漏 - 检查所有
try/except块,确保连接归还逻辑在finally或async with作用域内
如何让闲置连接不被数据库踢掉
PostgreSQL 默认 60 秒无活动就断开空闲连接,但 asyncpg 不会主动探测,导致后续协程拿到失效连接,抛出 ConnectionResetError 或 server closed the connection unexpectedly。
立即学习“Python免费学习笔记(深入)”;
- 给 pool 加参数:
max_inactive_connection_lifetime=300(5 分钟),让池自己回收真正闲置的连接 - 配合 OS 层 TCP keepalive:
tcp_keepalives_idle=60、tcp_keepalives_interval=10,比应用层 ping 更轻量 - 禁用
pool_pre_ping=True(SQLAlchemy)或手写SELECT 1检查:高频场景下它反而增加延迟,且不能解决连接中途断开的问题
pg_stat_activity 里长期 idle in transaction 的连接,它们就是泄漏源。


















