数据库连接池在协程await期间被提前关闭导致资源失效,根源是资源生命周期与协程不匹配;应全局初始化池、显式管理关闭、避免局部创建、取消时手动归还连接,并禁用__del__清理。

因为数据库连接或连接池在协程 await 期间被提前释放,而 Task 仍在事件循环中运行,导致底层资源失效。
asyncio.Task 被销毁的典型触发点
这不是协程逻辑出错,而是资源生命周期管理错位。常见于以下场景:
- 使用
async with获取数据库连接,但async with块已退出,连接被关闭,而协程仍在 await 后续操作(如cursor.fetchall()) - 连接池对象(如
aiomysql.Pool或asyncpg.Pool)在事件循环运行中被显式.close()或垃圾回收,但仍有未完成的 Task 持有其连接引用 - Dify、FastAPI 等框架中,请求作用域结束(如
Depends退出)后连接池被清理,但后台 Task 仍在执行
连接池未正确复用或过早关闭
异步连接池依赖事件循环存活,且需与协程生命周期对齐。若池在 Task 运行中途被关闭,后续所有基于该池的 await 都会失败,并可能抛出类似 Task was destroyed but it is pending! 或底层连接已关闭的异常(如 ConnectionClosedError)。
- 不要在函数内创建并立即
await pool.close(),除非你确定所有相关 Task 已完成 - 全局连接池应随应用启动初始化,随应用关闭才调用
.close();推荐用asyncio.create_task()+loop.shutdown_asyncgens()配合清理 - 检查是否误将连接池实例放在局部作用域(如 FastAPI 的
Depends函数内部),导致每次请求都新建池又丢弃
协程取消时未做资源隔离
当一个数据库查询 Task 被 task.cancel(),它不会自动释放连接——连接仍被挂起在已取消的 Task 中,直到事件循环彻底清理该 Task。若此时连接池尝试回收连接,就会因连接正被“已销毁但未清理”的 Task 占用而报错。
- 务必在数据库操作外层加
try/except asyncio.CancelledError,并在finally中显式归还连接(如await conn.close()或pool.release(conn)) - 避免直接
await conn.execute(...),优先用async with pool.acquire() as conn:确保自动归还 - 若用
asyncio.wait_for(..., timeout=...)包裹查询,注意它会取消 Task,但不保证连接同步释放;建议配合asyncio.shield()保护连接释放逻辑
容易被忽略的细节:事件循环退出顺序
最隐蔽的问题是:主程序调用 asyncio.run(main()) 结束时,事件循环开始关闭,但某些 Task 的 __del__ 或弱引用回调可能还在试图访问已解构的连接对象,从而触发“Task已被销毁”类异常。这类错误往往只在日志末尾出现,且无明确堆栈指向你的代码。
- 不要依赖
__del__或弱回调做连接清理;所有清理必须显式、同步地在async with或finally中完成 - 在
main()结束前,显式await pool.close()并await asyncio.sleep(0)让事件循环处理完 pending Task - 生产环境建议用
asyncio.Runner(Python 3.11+)替代asyncio.run(),可更可控地管理循环生命周期
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











