创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0 |
ORM 是什么,为什么需要它
ORM(Object-Relational Mapping)像是数据库和 Python 对象之间的翻译官:
Python: user.name = "Yardon"
↓ (ORM 翻译)
SQL: UPDATE users SET name = 'Yardon' WHERE id = 1;
不用写裸 SQL,不需要手动拼接查询字符串,不用处理不同数据库的方言差异。同时 ORM 自动帮你防 SQL 注入。
SQLAlchemy 2.0 快速上手
python
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"
engine = create_async_engine(DATABASE_URL, echo=False)
class Base(DeclarativeBase):
pass
async def get_db():
async with AsyncSession(engine) as session:
yield session
在 FastAPI 中注入数据库会话
python
from fastapi import Depends
@app.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).limit(10))
return result.scalars().all()
Depends 是 FastAPI 的依赖注入系统。get_db 被调用后返回一个数据库会话,路由函数参数中声明 db: AsyncSession 就能拿到它。
定义模型与关系
python
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(50), unique=True, index=True)
email: Mapped[str] = mapped_column(String(120))
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
# 一对多:一个用户有多篇文章
posts: Mapped[list["Post"]] = relationship(back_populates="author")
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(200))
content: Mapped[str] = mapped_column(Text)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
# 反向关系
author: Mapped["User"] = relationship(back_populates="posts")
异步数据库操作
python
# 创建
new_user = User(username="Yardon", email="y@example.com")
db.add(new_user)
await db.commit()
# 查询
result = await db.execute(select(User).where(User.id == 1))
user = result.scalar_one_or_none()
# 分页
result = await db.execute(
select(User).offset(skip).limit(limit).order_by(User.created_at.desc())
)
# 更新
user.email = "new@example.com"
await db.commit()
# 删除
await db.delete(user)
await db.commit()
数据库迁移:Alembic
数据库表结构会随着项目演进不断变化------加字段、改类型、建新表。Alembic 帮你管理这些变更。
bash
pip install alembic
alembic init alembic
bash
# 修改模型后:
alembic revision --autogenerate -m "add users table"
alembic upgrade head # 应用迁移
alembic downgrade -1 # 回滚一步(出问题时救命)
查询优化:N+1 问题
python
# ❌ N+1 问题
users = await db.execute(select(User))
for user in users.scalars():
posts = await db.execute(
select(Post).where(Post.user_id == user.id) # 每个用户1次查询!
)
# ✅ 预加载关系
users = await db.execute(
select(User).options(selectinload(User.posts))
)
for user in users.scalars():
print(user.posts) # 已经在第一步查好了
N+1 问题就像去图书馆借 10 本书,你不是一次拿 10 本,而是拿一本回家、再来拿第二本、再来拿第三本------效率极低。selectinload 就是一次把关联数据全查出来。
连接池与事务管理
python
engine = create_async_engine(
DATABASE_URL,
pool_size=10, # 常驻连接数
max_overflow=20, # 突发额外连接数
pool_recycle=3600, # 连接回收时间(秒)
)
事务管理:
python
async def transfer(sender_id, receiver_id, amount, db):
async with db.begin(): # 自动 commit/rollback
sender = await db.get(User, sender_id)
receiver = await db.get(User, receiver_id)
sender.balance -= amount
receiver.balance += amount
# 如果中间出错,两边都不会被修改
本章小结
SQLAlchemy 2.0 + asyncpg 是 FastAPI 的最佳数据库搭档。记住三条:用 selectinload 避免 N+1、用 Alembic 管理迁移、用 async with db.begin() 保证事务一致性。
📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top
📖 本章是「FastAPI 全栈后端」系列的第 3 章。
🌟 欢迎大家来观看!