news 2026/10/26 8:27:31

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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步:初始化与接线

pipinstallalembic alembic init migrations

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

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

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

importosfromcore.modelsimportBase 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设置环境变量

exportDATABASE_URL="postgresql+psycopg://postgres:你的密码@localhost:5432/daily"# 永久生效写进~/.zshrc

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

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

alembic revision--autogenerate-m"initial models"alembic upgradehead

🔍 autogenerate的原理

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

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


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

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

① 改models.py

classUser(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)# 新增...classArticle(Base):...view_count:Mapped[int]=mapped_column(server_default=0)# 新增

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

② 生成第二个迁移

alembic revision--autogenerate-m"add user email and article view_count"

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

defupgrade()->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))defdowngrade()->None:op.drop_column("articles","view_count")op.drop_column("users","email")

③ 执行

alembic upgradeheaddockerexec-itpg-daily psql-Upostgres-ddaily-c"\d articles"# view_count列已经在那了

④ 回滚演示(诚实版)

alembic downgrade-1

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

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

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


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

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

🎬 场景一:改列名

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

defupgrade()->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")

🎬 场景二:批量数据修正

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

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

七、验收清单

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

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

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

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

✅ 解法:

alembichistory# 核对历史

确需重置就清空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

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

✅ 解法:

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 8:57:57

基于W55MH32L-EVB的I2S音频播放实战指南

1. 整体设计与核心思路拆解1.1 这个标题到底在做什么“I2S? Playing Audio Using W55MH32L-EVB”这个标题,翻译过来就是用新唐 W55MH32L-EVB 开发板,通过 I2S 接口播放音频。I2S 是集成音频总线(Inter-IC Sound)的缩写&#xff0…

作者头像 李华
网站建设 2026/10/15 10:48:11

HarmonyOS:人脸识别

一、开发者准备应用在开发用户身份认证功能时,需要先了解以下流程,并根据实际需求参考对应指导开发。 查询设备支持的用户身份认证能力。发起身份认证请求,获取身份认证结果。校验和使用认证结果:请参考通用密钥库二次认证密钥访问…

作者头像 李华
网站建设 2026/10/18 19:47:34

问卷样本多少钱一份

做调研绕不开预算问题。问"一份多少钱"的人,往往在得到报价之后更困惑——有人报几元一份,有人报上百元一份,差距能到十倍以上。这篇文章把价格讲清楚:单价由什么决定、不同人群大致落在哪个区间、报价便宜和贵背后差在…

作者头像 李华
网站建设 2026/10/26 8:27:30

押上四倍年销售额做 LabVIEW,后来上了 57 家杂志封面

公司一年卖 250 万美元的货,却拿出 1000 万美元去做一个没人见过的软件。这不是段子,是 NI 真实干过的事。Physics Today 的访谈里,Kodosky 回忆:NI 在 LabVIEW 上的研发投入大约是 1000 万美元——"那时候我们的年销售额&am…

作者头像 李华
网站建设 2026/10/26 8:27:26

claude-mem实战:解决Claude会话失忆的AI记忆管理方案

你有没有遇到过这种情况:和Claude聊了一个下午,把项目背景、技术选型、踩坑结论都聊透了,第二天打开新会话,它一脸茫然地看着你,仿佛昨天什么都没发生过。我一开始以为是自己没把上下文写清楚,后来发现这根…

作者头像 李华
网站建设 2026/10/15 9:42:44

运算符重载该写成成员还是非成员

运算符重载最容易被忽略的一个决策是:这个函数写在类里还是写在类外。看起来只是风格问题,实际上它直接决定了表达式的对称性:同一个 ,写成成员时 2.0 v 编译不过,写成非成员就两边都能用。这篇把「谁该写在哪」讲到底…

作者头像 李华