使用sessionmaker封装函数时出现数据库连接过多问题的原因排查
使用sessionmaker封装函数时出现数据库连接过多问题的原因排查
核心问题:lru_cache误用导致Session实例被全局缓存
你的问题根源在于get_session()函数被@functools.lru_cache(maxsize=None)装饰了——这个装饰器会永久缓存函数的返回结果,意味着每次调用get_session()都会返回同一个Session实例,而不是每次都创建全新的会话对象。
详细分析流程
- 第一次调用
get_session():创建新的Session实例并缓存它,在with语句中使用后,会话被关闭,连接理论上应该被释放回连接池。 - 后续调用
get_session():由于缓存机制,直接返回之前已经被关闭的Session实例。当你再次用with包裹这个实例时,SQLAlchemy会尝试重新激活它,但这个过程会绕过正常的连接池管理逻辑,导致旧连接没有被正确回收,甚至每次复用都会创建新的连接却不释放,最终造成连接数持续增长。
对比你无包装器的代码myfun_withoutwrapper():每次都会创建全新的Session实例,with语句退出时会正确关闭会话并将连接归还到连接池,所以不会出现连接泄漏。
验证方法
你可以在get_session()中添加打印语句,确认每次返回的是同一个实例:
@functools.lru_cache(maxsize=None) def get_session() -> Session: sess = _get_sessionmaker()() print(f"Session instance ID: {id(sess)}") return sess
调用多次myfun()后,你会发现打印的ID完全相同,证明始终在复用同一个Session对象。
解决方案
- 移除
get_session()上的lru_cache装饰器:Session是有状态的会话对象,必须随用随创建,用完即销毁(通过with自动处理),绝对不能全局缓存。 - 保留
_get_sessionmaker()和get_engine()的缓存:这两个函数返回的是无状态的工厂类和引擎实例,缓存它们是合理的,可以避免重复创建引擎和会话工厂,提升性能。
修改后的get_session()应该是这样:
def get_session() -> Session: return _get_sessionmaker()()
这样每次调用get_session()都会返回全新的Session实例,with语句退出时也能正确关闭会话、释放连接回连接池,解决连接数过多的问题。
备注:内容来源于stack exchange,提问作者duepiert
相关产品推荐
相关产品推荐

