← 返回题目列表

FastAPI 中如何管理数据库连接和 Session?

高频 中等 第 12 / 27 题 更新于 2026/07/27
数据库SQLAlchemySession连接池

简化版

FastAPI 通常用依赖注入管理数据库 Session:每个请求创建一个 Session,处理完成后关闭;应用启动时可以初始化连接池,但不要多个请求共享同一个 Session。同步 ORM 用同步依赖,异步 ORM 或异步驱动则配合 async 依赖。

详细版

数据库管理要区分连接池和 Session:

  • 连接池是应用级资源,可以在应用生命周期中创建;
  • Session 是请求级资源,通常每个请求独立创建;
  • 请求结束后必须关闭 Session;
  • 事务要明确 commit、rollback;
  • 同步 SQLAlchemy 不要直接放进 async def 中长时间阻塞事件循环;
  • 异步 SQLAlchemy 要使用 AsyncSession 和异步驱动。

典型依赖:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

路由中:

def list_users(db: Session = Depends(get_db)):
    return db.query(User).all()

完整版教学

一、为什么数据库 Session 不能随便全局共享

数据库访问是 Web 项目最容易出问题的地方之一。很多初学者会把数据库连接或 Session 做成全局变量,然后所有请求都复用同一个对象。这在并发场景下很危险。

以 SQLAlchemy Session 为例,它不只是一个“连接”。它还维护了事务状态、对象缓存、脏数据跟踪等上下文。多个请求共享一个 Session,可能导致事务互相影响、数据状态污染、并发安全问题。

正确思路是:连接池可以是全局或应用级资源,但 Session 应该是请求级资源。每个请求拿一个 Session,用完关闭。

二、同步 SQLAlchemy 的典型写法

同步 SQLAlchemy 项目里常见结构是:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine(DATABASE_URL, pool_pre_ping=True)
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)

然后用依赖注入管理 Session:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

接口中声明:

@app.get("/users")
def list_users(db: Session = Depends(get_db)):
    return db.query(User).all()

yield 前创建 Session,路由执行时使用 Session,路由结束后关闭 Session。这是 FastAPI 中非常经典的数据库依赖模式。

三、事务如何处理

对于写操作,事务边界要清楚:

@app.post("/users")
def create_user(user_in: UserCreate, db: Session = Depends(get_db)):
    user = User(**user_in.model_dump())
    db.add(user)
    db.commit()
    db.refresh(user)
    return user

如果业务复杂,可以在 service 层统一处理 commit 和 rollback。无论放在哪里,都要避免“有些地方 commit,有些地方忘记 commit”的混乱状态。

更稳的写法是在依赖中加入异常回滚:

def get_db():
    db = SessionLocal()
    try:
        yield db
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

但这种写法意味着所有使用该依赖的接口都会在成功后 commit,是否合适要看项目风格。有些团队更喜欢业务代码显式 commit,因为事务边界更明显。

四、同步 ORM 和 async def 的关系

如果使用同步 SQLAlchemy,却把路由写成 async def,并在里面直接执行同步查询,查询期间会阻塞事件循环。并发高时会影响其他异步请求。

在同步 ORM 项目中,可以使用普通 def 路由,让 FastAPI 将其放入线程池:

@app.get("/users")
def list_users(db: Session = Depends(get_db)):
    return db.query(User).all()

如果项目希望全链路异步,就应该使用异步数据库驱动和 AsyncSession,而不是简单把函数改成 async def

五、异步 SQLAlchemy 的写法

异步 SQLAlchemy 通常使用 create_async_engineasync_sessionmaker

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

engine = create_async_engine(ASYNC_DATABASE_URL)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

查询也要用 await

@app.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User))
    return result.scalars().all()

异步数据库并不意味着所有场景都更快。它主要改善 I/O 等待期间的并发能力,同时也会引入更高的编程复杂度。

六、连接池配置要关注什么

生产环境要关注连接池大小、连接回收、连接健康检查和数据库最大连接数。Web worker 数量乘以每个进程的连接池大小,可能很快超过数据库承受能力。

例如 4 个 worker,每个 worker 连接池最多 20 个连接,就可能占用 80 个数据库连接。数据库如果最大连接数只有 100,再加上后台任务和运维连接,就容易出问题。

因此数据库连接池不是越大越好,要结合数据库能力、接口耗时、并发量和 worker 数量设置。

七、常见误区与追问

对象能否全局复用原因
Engine / 连接池可以作为应用级资源内部管理连接复用,创建成本较高
Session / AsyncSession不应该跨请求共享保存事务状态、对象状态和上下文
ORM Model 实例不建议长期跨请求持有可能绑定旧 Session 或携带过期状态
4 个 worker * 每个 pool_size=10 + max_overflow=20
理论峰值连接 = 4 * (10 + 20) = 120
如果数据库 max_connections=100,就可能在压测或流量峰值时耗尽连接。

记忆钩子:连接池是“公用水库”,Session 是“一次请求的一只水杯”,水杯不要多人共用。

第一个误区是全局共享 Session。连接池可以复用,Session 不应该跨请求复用。

第二个误区是忘记关闭 Session。长时间运行后连接池会被耗尽。

第三个误区是在异步接口里混用同步数据库驱动。代码看起来异步,实际仍然阻塞。

第四个误区是把 ORM 模型直接作为响应模型。ORM 模型代表数据库结构,外部 API 应该用单独的 Pydantic 输出模型。

  • 误区:全局共享 SQLAlchemy Session 可以减少开销。 Session 保存事务、缓存和对象状态,跨请求共享会造成数据污染和并发安全问题。
  • 误区:关闭 Session 等于关闭数据库连接池。 db.close() 通常是把连接归还连接池,而不是销毁整个 Engine;这正是请求级 Session 可频繁创建关闭的原因。
  • 误区:async def 路由里用同步 ORM 也没问题。 同步查询会阻塞事件循环;要么用普通 def 路由,要么切到异步驱动和 AsyncSession
  • 追问:事务 commit 应该放在哪里? 简单项目可在路由或 service 显式 commit;统一依赖自动 commit 也可行,但要保证事务边界清晰并正确 rollback。
  • 追问:连接池大小怎么估算? 至少要把 worker 数量、每个进程 pool_size、max_overflow、后台任务连接和数据库上限一起算,不能只看单进程配置。
  • 追问:为什么响应模型不要直接用 ORM 模型? ORM 模型代表表结构,可能泄漏内部字段,并让 API 契约和数据库演进强耦合。

八、加强记忆

记 FastAPI 数据库管理:连接池是应用级,Session 是请求级;Session 用 Depends + yield 创建和关闭;同步 ORM 配普通 def 更稳,异步 ORM 要使用 AsyncSession 和异步驱动;事务边界要明确,连接池大小要结合 worker 数量一起算。