如果你正在寻找一个高性能、易维护的后端数据服务解决方案,FastAPI 搭配 SQLAlchemy 2.0 和 MySQL 绝对值得考虑。这个组合在开发效率、运行性能和代码可维护性上都有出色表现。
FastAPI 是近年来最受欢迎的 Python Web 框架之一,它天生支持异步,性能接近 Node.js 和 Go。我在实际项目中使用后发现,它的自动文档生成和类型提示功能可以节省大量开发时间。而 SQLAlchemy 2.0 带来了全新的声明式 API 设计,代码更加简洁直观。MySQL 作为最流行的关系型数据库之一,稳定性无需多言。
这个技术栈特别适合以下场景:
在开始之前,确保你已经安装了 Python 3.7+ 环境。我推荐使用虚拟环境来管理依赖:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
然后安装核心依赖包:
bash复制pip install fastapi sqlalchemy pymysql pydantic
这里有几个值得注意的点:
pymysql 是 Python 连接 MySQL 的驱动pydantic 用于数据验证和序列化aiomysql登录 MySQL 创建一个新数据库:
sql复制CREATE DATABASE fastapi_demo
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
我强烈建议使用 utf8mb4 字符集,因为它支持完整的 Unicode 字符,包括 emoji。在实际项目中,字符集问题导致的乱码很难排查,从一开始就使用正确的配置能避免很多麻烦。
创建一个 database.py 文件来管理数据库连接:
python复制from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://user:password@localhost/fastapi_demo?charset=utf8mb4"
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
pool_pre_ping=True, # 自动检测连接是否有效
pool_size=5, # 连接池大小
max_overflow=10, # 超出pool_size后最多创建的连接数
pool_recycle=3600 # 连接回收时间(秒)
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
这里有几个实用的配置参数:
pool_pre_ping: 自动检查连接是否有效,避免因连接超时导致的错误pool_recycle: 定期回收连接,防止数据库连接超时max_overflow: 在高并发时允许创建额外连接如果你需要异步支持,可以这样配置:
python复制from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
ASYNC_DATABASE_URL = "mysql+aiomysql://user:password@localhost/fastapi_demo"
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 打印SQL语句,调试用
pool_pre_ping=True
)
AsyncSessionLocal = sessionmaker(
bind=async_engine,
class_=AsyncSession,
expire_on_commit=False
)
在 models.py 中定义你的数据模型。SQLAlchemy 2.0 的声明式语法更加简洁:
python复制from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255), unique=True, index=True)
hashed_password = Column(String(255))
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(100), index=True)
description = Column(String(500), nullable=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
SQLAlchemy 2.0 的类型注解更加完善,你可以使用 Python 原生类型来定义字段:
python复制from sqlalchemy.orm import Mapped, mapped_column
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True)
# 其他字段...
SQLAlchemy 的关系系统非常强大但也容易出错。relationship() 的常用参数:
back_populates: 双向关系,需要在关联的两个模型中都定义lazy: 加载策略,常见的有:
select (默认): 访问时加载joined: 使用JOIN立即加载subquery: 使用子查询加载dynamic: 返回可额外过滤的查询对象对于一对多关系,我推荐这样定义:
python复制# 在User模型中
items = relationship("Item", back_populates="owner", lazy="select")
# 在Item模型中
owner = relationship("User", back_populates="items", lazy="joined")
创建 schemas.py 文件来定义 Pydantic 模型:
python复制from typing import Optional, List
from pydantic import BaseModel, EmailStr
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
from_attributes = True # 旧版叫 orm_mode
class UserBase(BaseModel):
email: EmailStr # 专门验证邮箱格式
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
from_attributes = True
Pydantic V2 有几个重要改进:
orm_mode 改名为 from_attributes你可以在 Pydantic 模型中使用自定义验证器:
python复制from pydantic import field_validator
class UserCreate(UserBase):
password: str
@field_validator('password')
def password_complexity(cls, v):
if len(v) < 8:
raise ValueError("密码至少8个字符")
if not any(c.isupper() for c in v):
raise ValueError("密码必须包含大写字母")
return v
在 crud.py 中实现常见的数据库操作:
python复制from sqlalchemy.orm import Session
from . import models, schemas
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
hashed_password = pwd_context.hash(user.password)
db_user = models.User(email=user.email, hashed_password=hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
# 类似的item操作...
千万不要像示例中那样简单拼接密码!使用 passlib 进行正确的密码哈希:
python复制from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str):
return pwd_context.hash(password)
在 main.py 中创建 FastAPI 应用:
python复制from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 依赖项
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
# 其他路由...
对于分页查询,可以这样实现:
python复制from fastapi import Query
@app.get("/items/")
def read_items(
skip: int = 0,
limit: int = Query(default=100, le=1000), # 限制最大1000条
db: Session = Depends(get_db)
):
items = crud.get_items(db, skip=skip, limit=limit)
return items
对于复杂查询,可以使用查询参数:
python复制@app.get("/items/search/")
def search_items(
title: str = None,
description: str = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
query = db.query(models.Item)
if title:
query = query.filter(models.Item.title.contains(title))
if description:
query = query.filter(models.Item.description.contains(description))
return query.offset(skip).limit(limit).all()
在生产环境中,数据库连接管理尤为重要。我遇到过连接泄漏导致数据库崩溃的情况,所以建议:
python复制@app.get("/health")
def health_check(db: Session = Depends(get_db)):
try:
db.execute("SELECT 1")
return {"status": "healthy"}
except Exception as e:
raise HTTPException(status_code=500, detail="Database connection failed")
lazy="selectin" 加载关系数据SELECT *python复制from sqlalchemy.orm import selectinload
def get_user_with_items(db: Session, user_id: int):
return db.query(models.User).options(
selectinload(models.User.items)
).filter(models.User.id == user_id).first()
在实际项目中,我发现合理使用这些技巧可以将接口响应时间减少50%以上。特别是对于复杂的关系查询,正确的加载策略能显著提升性能。