asyncpg.Pool 关闭必须显式调用 close() 和 wait_closed(),否则连接泄漏;SQLAlchemy AsyncEngine 应 await engine.dispose() 而非 dispose_async();多池需按依赖逆序关闭,并用 try/catch 处理 wait_closed() 异常。

asyncpg.Pool 关闭时必须显式调用 close() 和 wait_closed()
asyncpg 的连接池不会在对象被垃圾回收时自动释放底层连接,不手动关闭会导致连接泄漏、端口耗尽或数据库报错 too many clients already。只调用 pool.close() 不够——它只是发起关闭流程,实际连接仍可能活跃;必须紧接着 await pool.wait_closed() 才算真正清理完毕。
常见错误写法:
await pool.close() # ❌ 缺少 wait_closed,连接可能未释放
正确做法:
- 在应用退出前(如 FastAPI 的
shutdown事件、aiohttp 的on_cleanup)触发关闭逻辑 - 始终用
await pool.close(); await pool.wait_closed()成对调用 - 若关闭过程超时(如网络卡顿、DB 响应慢),
wait_closed()会一直挂起,建议加asyncio.wait_for(..., timeout=5)包裹
SQLAlchemy 1.4+ async_engine 需要 dispose() 而非 dispose_async()
SQLAlchemy 1.4 引入了异步支持,但它的 create_async_engine() 返回的 AsyncEngine 并没有 dispose_async() 方法——这个函数名是社区误传的常见坑。真正可用的是 dispose(),它是协程函数,必须 await。
立即学习“Python免费学习笔记(深入)”;
典型误区:
await engine.dispose_async() # ❌ AttributeError: 'AsyncEngine' object has no attribute 'dispose_async'
正确方式:
- 调用
await engine.dispose(),它会逐个关闭所有空闲连接并等待活跃连接归还后释放 - 注意:如果仍有未提交/未关闭的
AsyncSession,dispose()会阻塞直到它们结束,因此务必确保 session 生命周期管理得当 -
engine.dispose()不会关闭正在使用的连接,只清理连接池中空闲连接和连接工厂状态
多个池共存时,关闭顺序不能颠倒:先业务池,再监控/日志池
当项目同时使用主库池、读写分离从库池、审计日志专用池时,关闭顺序影响稳定性。如果监控池先关,而主业务池关闭过程中还试图写审计日志,就会抛出 ConnectionResetError 或 InterfaceError: connection is closed。
安全顺序原则:
- 按依赖关系逆序关闭:被其他模块依赖的池(如日志池、指标上报池)最后关
- 主业务池(处理 HTTP 请求的)优先关闭
- 所有池的关闭操作建议统一收口到一个
shutdown_all_pools()协程里,用asyncio.gather()并发触发close(),但每个池内部仍需串行执行close()+wait_closed()
信号处理中关闭池容易漏掉异常:用 try/except 包住 wait_closed()
在监听 SIGTERM 或 SIGINT 后关闭池时,如果 DB 网络中断或服务已不可达,wait_closed() 可能抛出 ConnectionRefusedError 或 asyncpg.exceptions.TooManyConnectionsError,导致整个 shutdown 流程卡死或静默失败。
稳妥写法:
- 对每个池的
wait_closed()单独 try/catch,记录警告但不中断其他池关闭 - 避免在 finally 块里直接 await —— 若 event loop 已关闭,会报
RuntimeError: Event loop is closed - 推荐在 shutdown 协程开头检查
asyncio.get_event_loop().is_running(),再决定是否 await
最易被忽略的一点:连接池对象本身不是线程安全的,不要在 signal handler 的同步回调里直接 await 关闭协程——必须用 asyncio.create_task() 提交到 event loop 中执行。


















