数据库实战:FastAPI + SQLAlchemy 2.0 + Alembic 从零搭建,踩坑实录

写接口一时爽,改模型火葬场?别怕,这篇手把手带你避开所有暗礁。

📌 摘要

本文以实战为导向,从零开始搭建 FastAPI + SQLAlchemy 2.0 + Alembic 的数据库层。通过生活化比喻和踩坑案例,讲透异步引擎配置、模型定义新写法、迁移脚本生成与审核等核心环节,帮你建立一套可靠、可维护的数据库操作实践。适合所有被数据库折腾过的后端开发者。


🎯 开篇:你是不是也半夜被报警吵醒过?

"叮------" 线上服务报错了:sqlalchemy.exc.StatementError。原因?可能就是顺手改了个模型字段名,却忘了生成迁移脚本,导致生产环境表结构对不上。

这种经历,我相信不少朋友都有过。尤其是从 FastAPI + SQLAlchemy 起步的时候,异步怎么配?模型怎么写?Alembic 为什么总是识别不到变更?一个坑接一个坑。今天,咱们就来聊聊,从零到一搭一套靠谱的数据库层,顺便把我踩过的坑都给你标上警示牌。🎯

🚦 先理一下咱们要干的事

🔹 第一部分:为什么是这套组合? 聊聊选型逻辑

🔹 第二部分:手把手搭环境 从配数据库到写第一个模型

🔹 第三部分:Alembic 接入与避坑 自动生成脚本的那些坑

🔹 第四部分:进阶思考 连接池、异步、以及工程化建议

🍽️ 一、为什么是 FastAPI + SQLAlchemy 2.0 + Alembic?

咱们可以把 API 比作一家餐厅:FastAPI 是那个手脚麻利的点餐员 ,能快速把客人的需求(HTTP请求)传给后厨;SQLAlchemy 就是后厨的食材管理员 ,负责管理所有食材(数据)的进出和记录;而 Alembic 则是食材管理员的变更日志本,每次新增食材或调整存储方式,都得在本子上记一笔,保证后厨和仓库一致。

SQLAlchemy 2.0 之后,语法更清爽了,但同时也带来了一些变化------比如必须用 Mapped 和 mapped_column,如果你还抱着 1.x 的写法,跑起来就会报错。别问我怎么知道的,我第一次升级项目时,整个 models.py 一片飘红。

🛠️ 二、从零开始搭环境(附代码,可复制)

📦 安装依赖

先装好这些包,注意版本:

复制代码
pip install fastapi uvicorn sqlalchemy alembic asyncpg # asyncpg 是 PostgreSQL 异步驱动

这里多说一句:如果你用 MySQL,请装 aiomysql 或 asyncmy,千万别装错,否则异步引擎跑不起来。

🗂️ 项目结构

复制代码
myproject/
├── app/
│ ├── __init__.py
│ ├── database.py # 引擎、会话配置
│ ├── models.py # SQLAlchemy 模型
│ └── main.py # FastAPI 应用
├── alembic.ini
└── alembic/ # 迁移目录

🔌 配置数据库连接(database.py

这是最容易踩坑的地方。SQLAlchemy 2.0 推荐使用异步方式,但很多人照抄旧代码,配了个同步引擎,然后和 FastAPI 的异步路由打架。

复制代码
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"

# 创建异步引擎
engine = create_async_engine(DATABASE_URL, echo=True) # echo=True 会打印SQL,开发时很有用

# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

# 基类,所有模型都要继承它
class Base(DeclarativeBase):
    pass

注意看,我用了 create_async_engineasync_sessionmaker,这才是异步的正确姿势。echo=True 在开发时打开,能让你看到背后执行的 SQL,但线上记得关掉,否则日志会爆炸。

🧱 定义模型(models.py

SQLAlchemy 2.0 的新写法:用 Mappedmapped_column 替代老旧的 Column。刚开始我很不习惯,但用顺手后发现类型提示更香了。

复制代码
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(nullable=False)
    email: Mapped[str] = mapped_column(unique=True, index=True)
    age: Mapped[int | None] # 允许为空的字段,用 Optional 或 | None

注意:如果字段允许为空,类型注解必须用 | None ,否则 mapped_column 会默认 nullable=False。我一开始就漏了这个,导致插入数据时一直报错。

⚡ 在 FastAPI 中使用异步会话

接下来,咱们在 main.py 里写一个依赖,每次请求生成独立的会话:

复制代码
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import AsyncSessionLocal
from app import models

app = FastAPI()

async def get_db() -> AsyncSession:
    async with AsyncSessionLocal() as session:
        yield session

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

这里用了 async with 自动管理会话生命周期,用完即关,不会泄露连接。这是官方推荐的做法,比手动 close 靠谱多了。

🔄 三、Alembic 接入与自动迁移

现在模型有了,但数据库里还没表呢。这时候 Alembic 就该登场了。很多人觉得 Alembic 麻烦,但其实只要配一次,后面爽到飞起。

⚙️ 初始化 Alembic

复制代码
alembic init alembic

这会在项目根目录生成 alembic.ini 和 alembic/ 文件夹。然后打开 alembic.ini,修改数据库连接字符串:

复制代码
sqlalchemy.url = postgresql+asyncpg://user:pass@localhost/dbname

注意:这里必须用异步驱动格式,否则后面生成迁移时会报错。

🔧 修改 env.py 支持异步

这是最关键的一步!默认生成的 env.py 是给同步用的,我们需要改成异步模式。打开 alembic/env.py,找到 run_migrations_online 函数,修改如下:

复制代码
from sqlalchemy.ext.asyncio import async_engine_from_config
from sqlalchemy import pool
import asyncio

# 在 run_migrations_online 内部:
def do_run_migrations(connection):
    context.configure(connection=connection, target_metadata=target_metadata)
    with context.begin_transaction():
        context.run_migrations()

async def run_async_migrations():
    connectable = async_engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )
    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)

asyncio.run(run_async_migrations())

这段代码的作用是让 Alembic 用异步引擎连接数据库,然后通过 run_sync 调用同步的迁移配置。如果不改这里,你执行迁移时会得到 "asyncpg can't run in sync" 之类的错误。这个坑我替你们填了,直接复制就行。

还有一处要更改,否则会报错 env.py does not provide a MetaData object or sequence of objects to the context

复制代码
from app.models import User

# target_metadata = None
target_metadata = User.metadata

📝 生成并执行迁移

复制代码
alembic revision --autogenerate -m "init user table"
alembic upgrade head

运行第一条命令后,去 alembic/versions/ 下检查生成的脚本。千万别直接相信自动生成的脚本! 有时候它可能漏掉索引,或者把 nullable 搞反。我每次都会手动 review 一遍,比如看有没有创建唯一约束,有没有遗漏外键。这是线上事故的高发区。

确认无误后,执行 upgrade,表就建好了。以后每次修改模型,重复这两步即可。

🧠 四、进阶思考与注意事项

🔁 同步与异步的抉择

如果你是新项目,强烈建议用异步。但如果你维护的老项目是同步的,也别急着重构。同步配合 fastapi 的 run_in_threadpool 也能用,只是性能上限低一些。不过根据我的线上经验,大部分业务场景异步的收益并没有想象中大,除非你是高并发 IO 密集型。

💡 连接池调优

默认的连接池参数可能不适合你的并发量。在 create_async_engine 中可以调整:

复制代码
engine = create_async_engine(DATABASE_URL, pool_size=20, max_overflow=10)

pool_size 是核心连接数,max_overflow 是峰值时最多可以创建的额外连接数。

我通常会根据服务器的数据库最大连接数来设置,比如 MySQL 默认 151,那么 pool_size+max_overflow 最好不要超过 150,留一点给管理工具。

🚨 永远不要在迁移脚本中直接改数据

Alembic 是用来改表结构的,不是用来改数据的。如果你需要数据迁移(比如把用户名从两列合并成一列),请在upgrade 函数里用 op.execute() 执行原生 SQL,并且务必写好downgrade回退脚本。

血的教训:有一次我直接在脚本里写了 Python 循环更新数据,结果生产执行了半小时,锁表锁到崩溃。


👋 最后啰嗦一句

好了,这套组合拳打下来,你应该能顺利搭出一个稳当的数据库层了。其实这些工具都很成熟,难的是那些细枝末节的配置和习惯。我今天分享的每一个坑,都是真金白银换来的,希望你能绕过。

如果你觉得这篇文章有用,点赞、收藏、关注 走一波,下次找出来也方便。还有什么数据库相关的问题,欢迎留言,咱们一起讨论。毕竟,程序员最懂程序员,不是吗?😉

相关推荐
浦信仿真大讲堂10 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study11 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境12 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境12 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd12312 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy123931021612 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb0013 小时前
一个项目带你入门AI应用开发01
python
05664613 小时前
Python康复训练——常用标准库
开发语言·python·学习
昆曲之源_娄江河畔13 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql
05664614 小时前
Python康复训练——控制流与函数
开发语言·python·学习