【Web全栈进阶】Alembic数据库迁移:改表结构不再删库

之前的痛还记得吗:改了Model加字段,运行报table posts has no column named xxx------因为create_all()只建新表、不改旧表。

今天给数据库装上git :表结构变更进入版本管理,可升级、可回滚、可追溯。

🎯 本篇产出:Alembic接管早报站的表结构,两个真实迁移的生成与执行,一次回滚演示。含代码约80行。


📌 太长不看版(给想快速上手的你)

项目信息 一句话说明
本篇目标 用Alembic管理数据库表结构变更
代码行数 ~80行(迁移脚本+配置)
依赖 alembic(已装SQLAlchemy)
核心功能 autogenerate迁移 + 升级 + 回滚
跑起来的命令 alembic revision --autogenerate -m "描述" → alembic upgrade head
核心知识点 迁移脚本=数据库的commit、autogenerate原理、手写迁移
做完你能得到 数据库有了git:改表结构不再删库重建

🚨 核心认知 :迁移脚本只增不改。已经执行过的迁移脚本等于历史------改历史等于篡改,Alembic会用版本号对不上来抗议(第八节①)。


一、心智模型:迁移脚本 = 数据库的commit

Git是代码的安全网。Alembic把同样的安全网装到数据库上:

Git(代码) Alembic(表结构)
每次改动一个commit 每次变更一个迁移脚本
git log看历史 alembic history看历史
HEAD指针指向最新提交 alembic_version表里的版本号就是HEAD
git revert回退 alembic downgrade回退
分支合并 alembic merge(多人协作冲突时用)

📌 迁移脚本只增不改------改历史等于篡改。


二、第0步:初始化与接线

bash 复制代码
pip install alembic
alembic init migrations

生成alembic.ini + migrations/目录。接线两步,让Alembic认识你的模型和数据库:

📖 接线①:让它知道"表结构长什么样"

migrations/env.py里把target_metadata = None改为:

python 复制代码
import os
from core.models import Base

config = context.config
# 密钥军规:连接串走环境变量,不写死在配置里
config.set_main_option(
    "sqlalchemy.url",
    os.environ.get("DATABASE_URL",
                   "postgresql+psycopg://postgres:你的密码@localhost:5432/daily"))
...
target_metadata = Base.metadata

📖 接线②:让它知道"连哪个库"

上面的sqlalchemy.url已完成。

💡 注意postgresql+psycopg://:SQLAlchemy需要指明驱动。

🔧 macOS设置环境变量

bash 复制代码
export DATABASE_URL="postgresql+psycopg://postgres:你的密码@localhost:5432/daily"
# 永久生效写进~/.zshrc

三、第1步:第一个迁移------把存量表纳入管理

之前的表是create_all手工建的,Alembic还不认识它们。先autogenerate一次"对齐":

bash 复制代码
alembic revision --autogenerate -m "initial models"
alembic upgrade head

🔍 autogenerate的原理

📌 对比models.py(你声明的结构)与数据库(实际的结构),把差异写成迁移脚本。

打开生成的脚本看一眼------users / rss_sources / favorites三张缺的表被补上了,articles因为已存在而被跳过。


四、第2步:变更迭代------加字段的全流程

现在体验真正的高潮:给模型加两个字段。

① 改models.py

python 复制代码
class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    username: Mapped[str] = mapped_column(unique=True)
    email: Mapped[str | None] = mapped_column(default=None)   # 新增
    ...

class Article(Base):
    ...
    view_count: Mapped[int] = mapped_column(server_default=0)        # 新增

Tips: 注意view_count字段设置的是server_default=0,如果只设置default=0,这是Python层面插入新行进行的设置,不会进入DDL,如果数据库有字段,使用alembic进行字段的新增会导致字段非空而出现的错误。而设置server_default会写进DDL,不会出现错误。

② 生成第二个迁移

bash 复制代码
alembic revision --autogenerate -m "add user email and article view_count"

打开生成的脚本核对------op.add_column两条,正是差异所在:

python 复制代码
def upgrade() -> None:
    op.add_column("users", sa.Column("email", sa.String(), nullable=True))
    op.add_column('articles', sa.Column('view_count', sa.Integer(), server_default='0', nullable=False))


def downgrade() -> None:
    op.drop_column("articles", "view_count")
    op.drop_column("users", "email")

③ 执行

bash 复制代码
alembic upgrade head
docker exec -it pg-daily psql -U postgres -d daily -c "\d articles"
# view_count列已经在那了

④ 回滚演示(诚实版)

bash 复制代码
alembic downgrade -1

字段消失。再upgrade head回来------但注意:

🚨 回滚再升级,字段里的数据不会凭空回来。Alembic管结构,不自动管数据。

这就是为什么重要变更前要备份数据库。


五、第3步:手写迁移------autogenerate管不了的事

autogenerate只会对比表结构。两类场景需要手写迁移:

🎬 场景一:改列名

autogenerate会把它理解成"删旧列 + 加新列 "------数据全丢。安全做法是手写迁移,三步安全舞:

python 复制代码
def upgrade() -> None:
    # ① 加新列
    op.add_column("articles", sa.Column("author", sa.String(80)))
    # ② 把旧列数据搬过去
    op.execute("UPDATE articles SET author = title WHERE author IS NULL")
    # ③ 确认无误后(可分两次部署),再删旧列
    op.drop_column("articles", "title_copy")

🎬 场景二:批量数据修正

比如把所有空摘要填默认值:

python 复制代码
op.execute("UPDATE articles SET summary = '(暂无摘要)' WHERE summary = ''")

📌 纪律

🚨 手写迁移必须先在本地/测试库跑一遍,确认无误再上生产------迁移脚本也是代码,也要走"先测再上"。


六、命令速查(贴墙系列)

命令 作用
alembic init migrations 初始化(一次性)
alembic revision --autogenerate -m "描述" 对比模型生成迁移
alembic upgrade head 升级到最新
alembic upgrade +1 / downgrade -1 前进一步 / 回退一步
alembic history / alembic current 看历史 / 看当前版本
alembic heads 多人协作出现分叉时看heads

七、验收清单

bash 复制代码
1. alembic upgrade head → 迁移成功执行
2. psql \d articles → view_count列已存在
3. alembic history → 看到两条迁移记录
4. alembic downgrade -1 → 字段消失;upgrade head → 字段回归(但数据不回来)
5. alembic current → 显示当前版本号
6. 全程无报错后提交Git
bash 复制代码
git add .
git commit -m "Alembic接管表结构:两个迁移 + 回滚演示"

八、常见报错:这6个,Alembic的标配(重点!)

① FAILED: Can't locate revision identified by 'a1b2c3'

🔍 原因 :数据库的alembic_version里记录的版本号,在migrations/目录里找不到------删过旧迁移、或换了分支。

✅ 解法:

bash 复制代码
alembic history        # 核对历史

确需重置就清空alembic_version表重新stamp(谨慎)。


② Target database is not up to date

🔍 原因:有新迁移脚本还没执行。

✅ 解法 :alembic upgrade head。

📌 这也是部署流程的一部分:上线先upgrade(service前置命令)。


③ autogenerate生成了空迁移

🔍 原因 :env.py没接target_metadata------Alembic不知道你的模型。

✅ 解法:回到第2节接好线。

📌 生成空迁移时,先怀疑接线,再怀疑模型。


④ 改列名被autogenerate拆成"加列 + 删列"

🔍 原因:autogenerate只对比结构,不读你的心思。

✅ 解法:人工核对生成的脚本,改成第5节的"三步安全舞"。

📌 autogenerate是草稿,人工核对才是定稿。


⑤ 多人协作出现两个head

🔍 原因:你和同事各生成了一个迁移,历史分叉了。

✅ 解法:

bash 复制代码
alembic heads                          # 看到两个头
alembic merge heads -m "merge"         # 生成合并迁移

⑥ 生产库upgrade卡住 / 大表加列很慢

🔍 原因:大表加列会锁表(不同数据库行为不同)。

✅ 解法:

  • 低峰期执行;
  • 表特别大时了解数据库的在线加列方案。

🚨 迁移脚本上生产前,永远先在测试库跑一遍。


九、课后练习

# 练习 难度 提示
1 加列实战 :给Article加tags列,autogenerate + upgrade,psql验证 ⭐⭐ Mapped[str] = mapped_column(default="")
2 回滚演练 :downgrade -1再upgrade head,观察psql里列的消失与回归 ⭐⭐ 体会"结构回来、数据不回来"
3 数据迁移 :给favorites表加note列,并写一条op.execute给旧记录回填 ⭐⭐⭐ 手写迁移
4(选做) CI接入 :GitHub Actions里跑alembic upgrade head && pytest ⭐⭐⭐⭐ 迁移和测试一起进流水线

📦 配套代码

迁移脚本与env.py配置已上传Git(python_daily/):【gitee仓库地址】

相关推荐
曲径海盗36 分钟前
【Python 学习第 38 天】for 循环
python·学习笔记·编程入门
智能运维指南1 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
在世修行1 小时前
智学文枢:变式题生成引擎
python·ai·pyside·题目生成
znnnk1 小时前
数据库被AI“重写”:2026,从CPU中心走向GPU中心
数据库·人工智能·oracle
微微%1 小时前
mysql的安装和使用
数据库·mysql·mysql8安装
白山编程大哥2 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
On The Road :3 小时前
Java 多线程详解:从入门到线程安全
java·python·安全
软件派3 小时前
MiniMax M3.1 Flash 高频业务落地实战指南
大数据·数据库·人工智能
岁月宁静3 小时前
如何设计支持 Skills 的 Agent?一套可扩展的四层架构
python·agent
岁月宁静3 小时前
CrewAI 快速上手:10 分钟搭建你的第一个 Agent 团队
python·agent