跳到主要内容

7.4 最佳实践与下一步

项目跑通了,教程也接近尾声。本节做三件事:把散落全书的最佳实践汇总成清单、给出异步改造对照、规划你的下一步学习路线。

一、SQLAlchemy 最佳实践清单

架构与组织

  • 一个数据库一个 Engine,模块级全局;Session 每个工作单元一个,用完即关
  • ✅ 分层:routers 管 HTTP、crud 管数据库、models 管存储、schemas 管进出
  • ✅ crud 函数不 commit(最多 flush),谁代表完整业务谁 commit
  • ✅ 正式项目从第一天用 Alembic,别依赖 create_all

模型定义

  • String 永远写长度;金额用 Decimal + Numeric;固定选项用 enum.Enum
  • ✅ 每张表标配 created_at(server_default=func.now())与 updated_at(+onupdate)
  • ✅ 外键列加 index=True;常被 where/排序的列加索引
  • ✅ 每个模型写 __repr__
  • ✅ 父子从属关系配 cascade="all, delete-orphan";多对多和弱关联不配

查询与性能

  • ✅ 循环里访问关系属性前,必须 options(selectinload/joinedload) 预加载
  • ✅ 列表关系用 selectinload,单对象关系用 joinedload
  • ✅ 分页必须配 order_by;接口返回 total
  • ✅ 只需要两列就 select(User.name, User.email),别捞整个对象
  • ✅ 开发期开 echo=True,瞄一眼 SQL 条数是否随数据量增长(N+1 探测)

安全与健壮性

  • ✅ 原生 SQL 必须 text() + 参数化,严禁 f-string 拼接(SQL 注入)
  • ✅ 唯一性检查两道防线:先查后插给友好提示 + 唯一约束兜底捕获 IntegrityError
  • ✅ commit 失败必须 rollback
  • ✅ 密码只存哈希(bcrypt/argon2),出参 Schema 永远不含密码字段
  • ✅ 批量 update/delete 前把 where 条件读三遍
  • ✅ 生产配置(数据库密码等)放环境变量,不进代码库:
import os
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///app.db")
engine = create_engine(DATABASE_URL)

二、切换到 MySQL / PostgreSQL

项目要上线,把 SQLite 换成真正的服务器数据库:

# 1. 装驱动
# pip install pymysql (MySQL)
# pip install psycopg2-binary (PostgreSQL)

# 2. 改连接串(去掉 SQLite 专属的 check_same_thread)
engine = create_engine(
"mysql+pymysql://user:pass@localhost:3306/library?charset=utf8mb4",
pool_size=5,
max_overflow=10,
pool_recycle=3600,
pool_pre_ping=True, # 2.1 节的连接池参数在这里派上用场
)

模型、crud、路由零改动。(注:SQLite 里宽松通过的类型在 MySQL 会严格检查,跑一遍测试即可发现。)

三、异步版改造对照(衔接 5.4 节)

把实战项目改成异步只动两个文件,模式高度机械:

# database.py 异步版
from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker,
create_async_engine)

engine = create_async_engine("sqlite+aiosqlite:///library.db")
SessionLocal = async_sessionmaker(bind=engine, expire_on_commit=False)

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

DbSession = Annotated[AsyncSession, Depends(get_db)]
# 路由/crud:函数加 async,数据库调用加 await
@router.get("/books/{book_id}", response_model=schemas.BookOut)
async def get_book(book_id: int, db: DbSession):
book = await crud.get_book(db, book_id) # crud 内部同样 await
if book is None:
raise HTTPException(404, "图书不存在")
return book

注意事项回顾(5.4 节):expire_on_commit=False 必配;所有关系必须显式预加载(我们的 crud 层早就这么写了——这是分层 + 预加载习惯的红利)。

四、常见功能的下一步方向

实战项目还可以往这些方向扩展(每个都是真实工作中的高频需求):

功能关键技术提示
用户认证JWT + passlib(密码哈希)+ FastAPI 安全依赖官方教程 Security 章节写得很好
权限控制依赖注入链:get_current_user → require_admin依赖可以依赖另一个依赖
软删除is_deleted 标记(3.4 节)所有查询记得过滤
全文搜索PostgreSQL tsvector / MeiliSearchLIKE 在大数据量下太慢
缓存Redis 缓存热点查询注意缓存与数据库的一致性
后台任务FastAPI BackgroundTasks / Celery发邮件、生成报表
部署Docker + gunicorn/uvicorn + Nginx记得 echo=False、Alembic upgrade

五、学习资源

遇到问题的排查顺序:附录A 常见错误 → echo 日志看 SQL → 官方文档 → 搜索报错信息。

六、全教程知识地图(毕业检查)

对照下表自查,能独立完成右列任务即为掌握:

章节毕业标准
1-2 章不看资料写出:engine + Base + 一个带各种字段类型的模型 + create_all
3 章独立实现任意实体的 CRUD 函数,含动态搜索和分页
4 章为"订单-商品-用户"设计出正确的表关系,知道每处外键放哪、何时预加载
5 章会写 join + group_by 统计;会给项目接入 Alembic 并完成一次加字段迁移
6 章独立搭出 database/models/schemas/main 四件套并解释每个零件的作用
7 章向别人讲清楚借书接口为什么必须用事务,并能写测试证明它

🎓 结语

从"什么是数据库"到分层架构的完整 API 项目,你已经走完了大多数 Python 后端工程师的必经之路。接下来最好的学习方式是:用这套技术栈做一个你自己想要的项目——记账本、博客、追番清单,什么都行。真实需求会逼你把知识用活。

回到目录 → README | 遇到报错 → 附录A 常见错误与解决