【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仓库地址】

相关推荐
AI你一生一世1 小时前
当构建工具成为基础设施:从 Tailwind 被收购谈起,前端工具链的“归属”难题
前端·构建工具·tailwind css·前端工具链·开源软件收购·技术中立性·前端基础设施
杨云龙UP1 小时前
Oracle 19c 单机到单机 Active Data Guard 搭建与巡检操作手册
数据库·oracle·adg·操作指南·data guard·dg搭建
_zxd1 小时前
TypeScript 类型树
前端·typescript
Evan_Lai1 小时前
(一)独热编码、标签编码、目标编码、序数编码详解
python·机器学习
念越1 小时前
初中语数英答题与竞赛平台
java·数据库·spring boot
用户7624117567051 小时前
第二章 Python语言基础
python
129Lab1 小时前
BMS 核心算法:SOC 估算从安时积分到 LSTM-UKF 串级融合的 30 年演进(附 Python 实现)
python·算法·lstm·python3.11·bms·soc估算
129Lab1 小时前
电池热管理仿真的AI加速:用Python+PINN物理信息神经网络替代传统CFD的可行性探索
pytorch·python·cfd·pinn·物理信息神经网络·仿真加速·电池热管理