数据库迁移

数据库迁移用于记录数据库结构的变化,例如新增表、修改字段、增加索引或调整约束。在 Flask 项目中,常见组合是 SQLAlchemy + Flask-Migrate。Flask-Migrate 基于 Alembic,可以把模型变化生成迁移脚本,并把脚本应用到开发、测试和生产数据库。

迁移的目标不是“自动改库”这么简单,而是让团队可以追踪、审查和重复执行数据库结构变更。

安装与初始化

pip install Flask-Migrate

在应用中绑定扩展:

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
migrate = Migrate()

def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"

    db.init_app(app)
    migrate.init_app(app, db)

    return app

首次使用时生成迁移目录:

flask db init

这会创建 migrations/ 目录。这个目录应该提交到 Git,因为它保存了项目的数据库版本历史。

基本工作流

修改模型后,生成迁移脚本:

flask db migrate -m "add users table"

检查生成的迁移文件,确认字段、索引和约束符合预期,然后应用到数据库:

flask db upgrade

查看当前数据库版本:

flask db current

回滚上一个版本:

flask db downgrade

示例:新增字段

假设原来的用户模型只有 idemail

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)

现在增加昵称字段:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)
    nickname = db.Column(db.String(80), nullable=True)

执行:

flask db migrate -m "add user nickname"
flask db upgrade

生成脚本后一定要打开检查。自动检测通常能识别新增字段,但复杂变更可能需要手工调整。

团队协作流程

推荐流程:

  1. 修改 SQLAlchemy 模型。
  2. 执行 flask db migrate -m "clear message"
  3. 审查迁移脚本,避免误删字段或表。
  4. 在本地执行 flask db upgrade
  5. 运行测试。
  6. 将模型变更和迁移脚本一起提交。

团队成员拉取代码后,只需要执行:

flask db upgrade

即可把本地数据库更新到最新结构。

生产环境注意事项

  • 执行迁移前备份数据库。
  • 先在 staging 环境跑一遍迁移。
  • 避免在高峰期执行耗时的表结构变更。
  • 对大表新增非空字段时,优先分阶段处理:先允许为空,再回填数据,最后添加非空约束。
  • 不要直接修改已经发布并被别人使用的旧迁移脚本,应新增迁移。

常见问题

migrate 没有检测到变化

确认模型已经被应用导入。如果模型文件没有被加载,Alembic 无法比较模型和数据库结构。

生成了错误的删除操作

不要直接执行。先检查是否改了表名、字段名或模型导入路径。重命名字段时,自动检测可能会识别成“删除旧字段 + 新增新字段”,这会导致数据丢失,需要手工改为 rename 操作。

数据库版本和代码不同步

可以先查看当前版本:

flask db current
flask db heads

如果数据库已经手工调整到目标结构,但迁移版本未记录,可以谨慎使用:

flask db stamp head

stamp 只标记版本,不会真正执行结构变更,使用前必须确认数据库结构已经正确。

小结

Flask 数据库迁移的核心习惯是:模型变更和迁移脚本一起提交,迁移脚本先审查再执行,生产环境先备份再升级。把迁移当作代码的一部分管理,可以显著降低团队协作和上线时的数据库风险。