连接池耗尽主因是配置错误、归还遗漏和闲置连接失效三者叠加。asyncpg max_size需按worker数×单池上限≤数据库max_connections的70%~80%设置,必须用async with acquire,启用max_inactive_connection_lifetime和tcp keepalive,并为查询设command_timeout。

连接池耗尽不是并发太高,而是配置错 + 归还漏 + 闲置死 —— 三者叠加才爆。
asyncpg.create_pool 的 max_size 别乱设
max_size 是单个 pool 实例的并发上限,但 FastAPI 用 Gunicorn 启动 4 个 worker,实际连接数就是 max_size × 4。PostgreSQL 默认 max_connections=100,你设 max_size=30 就已超限,再加备份连接和 psql 会话,立刻报 TooManyConnectionsError。
- PostgreSQL 单实例建议总应用连接 ≤ 80(留 20% 余量),MySQL 则别超
max_connections的 70% - 别按 CPU 核数设:Python 的 GIL 和 I/O 等待让真实并发远低于理论值
- 起步建议
min_size=5、max_size=20,压测时盯pg_stat_activity中的active连接数再调
async with pool.acquire() 是唯一安全写法
手动 pool.acquire() + pool.release() 极易漏掉 release,尤其在异常路径里 —— 连接就永远卡在“已分配未归还”状态,池子越用越小。
- 必须用
async with pool.acquire() as conn:,哪怕内部抛异常,__aexit__也会自动归还 - 检查所有
try/except块:如果用了手动 acquire,release必须包在finally里 - 禁止跨协程复用同一个
AsyncSession或conn对象,这是泄漏高发区
空闲连接被数据库踢断后拿不到错误提示
PostgreSQL 默认 60 秒无活动就断开空闲连接,但 asyncpg 不主动探测,下次协程拿到这个“失效连接”,直接抛 ConnectionResetError 或 server closed the connection unexpectedly —— 表面看是随机报错,实则是池里混入了僵尸连接。
- 加参数
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:高频下它反而增加延迟,且解决不了连接中途断开的问题
慢查询会锁死整个连接池
asyncpg 默认没有单条查询超时。一个没索引的 ORDER BY 卡住 30 秒,那个连接就被独占 30 秒 —— 如果池里只有 10 个连接,后续 9 个请求全得排队等,形成雪崩。
- 给每个查询加
command_timeout,比如await conn.fetch("SELECT ...", timeout=5.0) - 或在 pool 初始化时统一设
command_timeout=5.0,避免漏配 - 配合数据库监控查
pg_stat_activity里长时间active的query,定位慢 SQL
最麻烦的不是参数调不对,而是泄漏藏在“看起来没问题”的分支里:比如某个异常日志上报逻辑里忘了用 async with,或者健康检查接口绕过了主流程直接 acquire 但没 release —— 这类路径压测时未必触发,上线后流量一波动就暴露。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











