查看: 79|回复: 3

FastAPI + SQLAlchemy + PyMySQL 连接 My

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在 FastAPI 后端中接入 MySQL,不只是写一条连接串那么简单。我们需要同时解决驱动选择、ORM 映射、会话管理、事务控制以及项目结构等问题。下面以同步版为例,梳理一条从安装依赖到增删改查的完整链路。

一、依赖安装
项目需要以下核心库:
  1. pip install "fastapi[standard]" sqlalchemy pymysql pydantic-settings
复制代码
如果使用新版 MySQL 认证方式(如 caching_sha2_password),可以加装 RSA 支持:
  1. pip install "pymysql[rsa]"
复制代码
正式环境建议额外安装 Alembic 管理数据库迁移:
  1. pip install alembic
复制代码

二、环境配置
数据库连接串和开关参数放在 .env 文件中,避免硬编码:
  1. .env
  2. DATABASE_URL="mysql+pymysql://root:password@127.0.0.1:3306/todo_api?charset=utf8mb4"
  3. DATABASE_ECHO=false
  4. DATABASE_AUTO_CREATE=false
复制代码
用 pydantic-settings 读取:
  1. from pydantic_settings import BaseSettings, SettingsConfigDict
  2. class Settings(BaseSettings):
  3.     model_config = SettingsConfigDict(
  4.         env_file=".env",
  5.         env_file_encoding="utf-8",
  6.     )
  7.     database_url: str
  8.     database_echo: bool = False
  9.     database_auto_create: bool = False
  10. settings = Settings()
复制代码

三、创建 Engine 与 SessionLocal
Engine 是数据库入口,管理连接池:
  1. from sqlalchemy import create_engine
  2. from app.core.config import settings
  3. engine = create_engine(
  4.     settings.database_url,
  5.     echo=settings.database_echo,
  6.     pool_pre_ping=True,   # 取出连接前检查是否存活
  7. )
复制代码
SessionLocal 是一个会话工厂,每个请求创建一个独立会话:
  1. from sqlalchemy.orm import sessionmaker
  2. SessionLocal = sessionmaker(
  3.     bind=engine,
  4.     autoflush=False,
  5.     autocommit=False,
  6.     expire_on_commit=False,
  7. )
复制代码

四、定义 ORM 模型
首先创建基类:
  1. from sqlalchemy.orm import DeclarativeBase
  2. class Base(DeclarativeBase):
  3.     pass
复制代码
接着定义业务模型,以 User 为例:
  1. from datetime import datetime
  2. from sqlalchemy import DateTime, Integer, String, func
  3. from sqlalchemy.orm import Mapped, mapped_column
  4. from app.db.base import Base
  5. class User(Base):
  6.     __tablename__ = "users"
  7.     id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
  8.     username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
  9.     email: Mapped[str] = mapped_column(String(191), unique=True, nullable=False)
  10.     status: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
  11.     created_at: Mapped[datetime] = mapped_column(
  12.         DateTime(timezone=True),
  13.         server_default=func.now(),
  14.         nullable=False,
  15.     )
复制代码

五、增删改查实现
1. 查询
  1. from sqlalchemy import select
  2. from sqlalchemy.orm import Session
  3. def list_users(db: Session) -> list[User]:
  4.     return list(db.scalars(select(User)).all())
  5. def get_user(db: Session, user_id: int) -> User | None:
  6.     return db.get(User, user_id)
  7. def get_user_by_username(db: Session, username: str) -> User | None:
  8.     return db.scalar(select(User).where(User.username == username))
  9. def page_users(db: Session, page: int, page_size: int) -> dict:
  10.     offset = (page - 1) * page_size
  11.     total = db.scalar(select(func.count()).select_from(User)) or 0
  12.     items = db.scalars(
  13.         select(User).order_by(User.id.desc()).offset(offset).limit(page_size)
  14.     ).all()
  15.     return {
  16.         "items": list(items),
  17.         "total": total,
  18.         "page": page,
  19.         "page_size": page_size,
  20.     }
复制代码

2. 新增
  1. def create_user(db: Session, username: str, email: str) -> User:
  2.     user = User(username=username, email=email, status=1)
  3.     db.add(user)
  4.     db.commit()
  5.     db.refresh(user)
  6.     return user
复制代码
注意:commit 后必须 refresh 才能拿到数据库生成的自增 ID 和默认时间戳。

3. 更新
  1. def update_user_email(db: Session, user_id: int, email: str) -> User | None:
  2.     user = db.get(User, user_id)
  3.     if user is None:
  4.         return None
  5.     user.email = email
  6.     db.commit()
  7.     db.refresh(user)
  8.     return user
复制代码
直接从会话查出的对象,修改字段后 commit 即可生成 UPDATE。

4. 删除
  1. def delete_user(db: Session, user_id: int) -> bool:
  2.     user = db.get(User, user_id)
  3.     if user is None:
  4.         return False
  5.     db.delete(user)
  6.     db.commit()
  7.     return True
复制代码

六、事务管理
写操作建议用 try/commit/rollback 包裹,或者使用 sessionmaker 的 begin 上下文:
  1. from sqlalchemy.orm import Session
  2. def create_user_with_role(db: Session, user: User, role: Role) -> User:
  3.     try:
  4.         db.add(user)
  5.         db.add(role)
  6.         db.commit()
  7.         db.refresh(user)
  8.         return user
  9.     except Exception:
  10.         db.rollback()
  11.         raise
复制代码
等价写法:
  1. with SessionLocal.begin() as db:
  2.     db.add(user)
  3.     db.add(role)
复制代码

七、在 FastAPI 中注入 Session
每个请求应获取独立会话,依赖注入是最佳实践:
  1. from collections.abc import Generator
  2. from sqlalchemy.orm import Session
  3. from app.db.session import SessionLocal
  4. def get_db() -> Generator[Session, None, None]:
  5.     db = SessionLocal()
  6.     try:
  7.         yield db
  8.     finally:
  9.         db.close()
复制代码
路由中使用:
  1. from typing import Annotated
  2. from fastapi import APIRouter, Depends
  3. from sqlalchemy.orm import Session
  4. from app.dependencies.database import get_db
  5. router = APIRouter(prefix="/users")
  6. @router.get("/")
  7. def get_users(db: Session = Depends(get_db)):
  8.     # ...
复制代码
也可以定义类型别名:
  1. SessionDep = Annotated[Session, Depends(get_db)]
复制代码

八、推荐工程结构
一个可维护的目录布局示例:
  1. app/
  2. ├── core/
  3. │   └── config.py          # 配置读取
  4. ├── db/
  5. │   ├── base.py            # DeclarativeBase
  6. │   ├── session.py         # engine 和 SessionLocal
  7. │   └── models.py          # 汇总导入所有 ORM 模型
  8. ├── dependencies/
  9. │   └── database.py        # get_db 依赖
  10. ├── modules/
  11. │   └── users/
  12. │       ├── models.py      # User ORM 模型
  13. │       ├── schemas.py     # Pydantic 数据模型
  14. │       ├── repository.py  # 数据库查询封装
  15. │       ├── service.py     # 业务逻辑
  16. │       └── router.py      # 路由定义
  17. ├── api/
  18. │   └── v1/
  19. │       └── router.py      # 聚合所有模块路由
  20. └── main.py                # FastAPI 应用入口
复制代码

九、常见错误排查
1. 忘记提交事务:写操作完成后必须调用 db.commit(),否则数据不会写入。
2. 异常后未回滚:捕获异常后应调用 db.rollback() 释放会话状态,否则后续查询可能报“Pending rollback”错误。
3. 在路由中直接创建 Engine:每个路由函数都创建新的 Engine 会导致连接泄漏,应将 Engine 作为全局单例。
4. 密码硬编码:数据库密码应放在环境变量或 .env 文件中,切勿写死在代码仓库。
5. 模型未被导入:如果使用 Base.metadata.create_all 自动建表,必须确保所有模型模块被提前导入,否则对应表不会创建。

全文围绕 FastAPI + SQLAlchemy + PyMySQL 构建 MySQL 后端,覆盖配置、模型、CRUD、事务与依赖注入,适合作为工程初始化的基础参考。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: FastAPI + SQLAlchemy + PyMySQL 连接 My

帖子写得很详细,从依赖安装到环境配置、ORM定义再到增删改查,逻辑清晰,操作性强。特别是用 pydantic-settings 读取 .env 的写法很规范,避免硬编码。有一点想确认:同步版中 SessionLocal 的 `expire_on_commit=False` 是为了避免 commit 后对象过期导致的懒加载问题吗?我遇到过类似场景,这样设置后确实方便很多。 另外,如果后续要升级到异步版(比如用 asyncmy 或 aiomysql),结构和这里类似,只是 Engine 和 Session 的创建方式不同。期待楼主后续能补一个异步版本的对比。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: FastAPI + SQLAlchemy + PyMySQL 连接 My

这个教程写得非常清晰完整!从依赖安装、环境配置到模型定义、CRUD实现,每一步都解释得很到位。特别是 `pool_pre_ping=True` 和 `expire_on_commit=False` 这两个参数,很多人容易忽略,能有效避免连接残留问题。另外用 pydantic-settings 读 .env 的方式也很规范。 想请问一下,如果项目中需要写事务(比如同时更新多张表),在 FastAPI 依赖注入里通常会怎么处理呢?是用 `db.begin()` 显式控制,还是依赖 Session 的自动提交/回滚机制?期待能补充一下事务协作方面的示例,谢谢!
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: FastAPI + SQLAlchemy + PyMySQL 连接 My

感谢楼主的详细梳理!从依赖安装到环境配置再到模型定义,每一步都很清晰,尤其是 `pool_pre_ping` 和 `expire_on_commit` 这些细节,对生产环境很有参考价值。不过注意到你只展示了查询部分的代码,想问一下:在同步版中,你通常如何组织增删改操作?比如使用 `db.add`、`db.commit`、`db.refresh` 时,事务提交和异常回滚的逻辑是放在 Service 层还是通过中间件统一管理?另外 FastAPI 的请求上下文中依赖注入 Session 的方式是否也有示例?期待后续的完整 CRUD 和项目结构分享~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-9 12:05 , Processed in 0.031749 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部