You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI结合SQLAlchemy与PostgreSQL的连接泄漏问题排查求助

FastAPI结合SQLAlchemy与PostgreSQL的连接泄漏问题排查求助

我现在碰到了FastAPI项目里的数据库连接泄漏问题——连接没法正常关闭,一直处于idle状态。尽管我已经尽可能谨慎地管理连接,空闲连接数还是持续上涨,最后甚至会触达数据库的最大连接限制,导致服务异常。

项目基本信息

  • 框架:FastAPI v0.104.0
  • Web服务器:Uvicorn v0.23.0,配置24个worker进程
  • 数据库工具:SQLAlchemy v2.0.21
  • 数据库:Docker部署的PostgreSQL 16

PostgreSQL的Docker Compose配置

postgres:
  image: postgres:16
  container_name: postgres_db
  ports: 
  volumes:
    - postgres_data:/var/lib/postgresql/data
  environment:
    POSTGRES_USER: ${POSTGRES_USER}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    POSTGRES_DB: ${POSTGRES_DB}
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
    interval: 300s
    timeout: 5s
    retries: 3

SQLAlchemy连接池配置

DATABASE_URL = os.environ.get("DATABASE_URL")

engine = create_engine(
    DATABASE_URL,
    pool_size=24,  # 连接池常驻连接数
    max_overflow=12,  # 连接池允许临时扩容的额外连接数
    pool_timeout=30,  # 获取连接的超时等待时间
    pool_recycle=1800,  # 连接自动回收周期(30分钟)
    pool_pre_ping=True,  # 使用前自动验证连接有效性
)

数据库会话管理器实现

class DatabaseManager:
    _instance = None

    def __new__(cls):
        if not cls._instance:
            cls._instance = super(DatabaseManager, cls).__new__(cls)
            cls._instance.engine = engine
            cls._instance.session_maker = scoped_session(
                sessionmaker(autocommit=False, autoflush=False, bind=cls._instance.engine)
            )
        return cls._instance

    def get_session(self):
        session = self._instance.session_maker()
        try:
            yield session
        finally:
            session.close()
            self._instance.session_maker.remove()

登录路由代码

@user_router.post("/login")
def login_user(
    request: Request,
    login_data: LoginRequest,
    usermanager=Depends(UserManager),
    db_session: Session = Depends(DatabaseManager().get_session),
):
    return usermanager.login_user(request, login_data, db_session)

登录核心逻辑

def login_user(self, request, login_data, db_session: Session):
    try:
        user = self.authenticate_user(request, login_data, db_session)
        if not user:
            raise HTTPException(status_code=401, detail="Incorrect username or password")
        
        if user.is_active:
            tokens = self.create_tokens_for_user(user)
            return tokens
        else:
            raise HTTPException(status_code=401, detail="User is inactive")

    except HTTPException as e:
        self.log_exception("login", e.status_code, e.detail, traceback.format_exc())
        raise e

    except Exception as e:
        try:
            self.log_exception("login retry", 500, str(e), traceback.format_exc())
            db_session = next(DatabaseManager().get_session())
            user = self.authenticate_user(request, login_data, db_session)
            if user and user.is_active:
                return self.create_tokens_for_user(user)
        finally:
            db_session.close()

        raise HTTPException(status_code=500, detail="An error occurred while logging in")

问题具体表现

我用以下SQL查询监控PostgreSQL的连接状态:

SELECT state, count(*) 
FROM pg_stat_activity 
GROUP BY state;

某次查询的输出如下:

state  | count
-------+-------
       |     5
active |     1
idle   |    73

空闲连接数一直在持续上涨!我原本预期最多只有24个空闲连接(对应连接池配置的pool_size),但实际数量不断攀升,直到触达数据库的最大连接限制。

在连接数达到上限前,我还偶尔会遇到这个错误:

sqlalchemy.exc.InvalidRequestError: This session is provisioning a new connection; 
concurrent operations are not permitted

当连接数触达上限后,就会触发这个致命错误:

psycopg2.OperationalError: connection to server at "postgres", port 5432 failed: 
FATAL:  sorry, too many clients already

额外观察结果

即使空闲连接数已经达到上限,登录函数里的重试逻辑居然还能正常工作。我已经确保所有未使用FastAPI依赖注入的会话都通过finally块手动关闭,而且用的是scoped_session(每个线程维护一个会话),但问题依然存在。

我试过各种能想到的办法,甚至找AI工具帮忙,但都没能解决这个问题。

补充测试数据

我运行了一个应用的克隆实例,完全不处理任何请求,14小时后的连接统计结果符合预期:

p_db=# SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
 state  | count 
--------+-------
        |     5
 active |     1
 idle   |    24
(3 rows)

这和scoped_session的特性一致——每个worker线程对应一个连接,正好是24个。

但在处理登录请求的主应用里,连接统计结果却完全失控:

p_db=# SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
 state  | count 
--------+-------
        |     5
 active |     1
 idle   |    89

我完全搞不懂为什么主应用的空闲连接数会无限制增长!而且即使出现“too many clients already”的错误,登录的重试尝试居然还能正常执行,这太诡异了。

更新排查进展

我甚至完全移除了连接池的配置,但问题还是一模一样,现在不知道该怎么进一步排查了。

我查询了空闲连接的详细信息,结果行数和空闲连接数一致,这里只复制了部分记录:

p_db=# SELECT pid, state, query, backend_start, query_start, client_addr 
FROM pg_stat_activity 
WHERE state = 'idle';
 pid | state |  query   |         backend_start         |          query_start          | client_addr 
-----+-------+----------+-------------------------------+-------------------------------+-------------
  35 | idle  | ROLLBACK | 2025-01-17 10:08:44.755878+00 | 2025-01-17 10:31:27.250991+00 | 172.30.30.6
  36 | idle  | ROLLBACK | 2025-01-17 10:08:44.77771+00  | 2025-01-17 10:37:32.826234+00 | 172.30.30.6
  37 | idle  | ROLLBACK | 2025-01-17 10:08:45.007471+00 | 2025-01-17 10:38:24.326404+00 | 172.30.30.6
  38 | idle  | ROLLBACK | 2025-01-17 10:08:45.064301+00 | 2025-01-17 10:22:07.79541+00  | 172.30.30.6
  39 | idle  | ROLLBACK | 2025-01-17 10:08:45.139814+00 | 2025-01-17 10:22:04.841665+00 | 172.30.30.6

其中172.30.30.6是我的FastAPI容器的IP地址。

备注:内容来源于stack exchange,提问作者Hassan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 13:54:53