Skip to content

数据库迁移

Flask-Migrate 是一个用于管理数据库迁移的 Flask 扩展,它基于 Alembic 构建。

Alembic 是一个轻量级的数据库迁移工具,专门为 SQLAlchemy 设计。

通过 Flask-Migrate,你可以轻松地管理和应用数据库模式的变化,而不需要手动编写 SQL 脚本或直接修改数据库结构。

主要作用

  1. 版本控制

    • Flask-Migrate 允许你对数据库模式进行版本控制。每次你修改了数据库模型(如添加、删除或更改表字段),都可以创建一个新的迁移脚本来记录这些变化。
  2. 自动迁移

    • 它可以自动生成迁移脚本,基于模型定义的变化。这大大减少了手动编写复杂 SQL 语句的工作量,并降低了出错的可能性。
  3. 逐步更新

    • 使用 Flask-Migrate 可以确保数据库模式按照预定的顺序逐步更新,从而避免一次性大规模变更带来的风险。这对于生产环境尤其重要,因为它允许你在不影响服务的情况下安全地部署新特性或修复问题。
  4. 回滚支持

    • 如果在某个迁移之后发现错误或者需要恢复到之前的版本,Flask-Migrate 提供了方便的回滚功能,使你可以撤销最近的一次或多次迁移。
  5. 多数据库支持

    • 支持多种数据库后端,包括 SQLite, PostgreSQL, MySQL 等等。
  6. 与 Flask-SQLAlchemy 集成良好

    • Flask-MigrateFlask-SQLAlchemy 配合得非常好,能够很好地适应 Flask 应用程序中常见的 ORM 模式。

使用场景

  • 开发阶段:在开发过程中频繁地调整数据库模型时,Flask-Migrate 可以帮助开发者快速迭代数据库设计。
  • 测试环境:在测试环境中,你可以使用迁移来同步不同开发人员之间的数据库状态,保证所有人的工作环境一致。
  • 生产环境:当应用程序上线后,随着业务需求的变化,数据库模式也需要相应地调整。此时,Flask-Migrate 可以确保这些变化被有序且安全地应用到生产数据库上。

安装依赖

shell
pip install flask-migrate

初始化迁移仓库

bash
export FLASK_APP=app.py
flask db init

执行后输出

shell
Creating directory '/case2_migrate_db/migrations' ...  done
Creating directory '/case2_migrate_db/migrations/versions' ...  done
Generating case2_migrate_db/migrations/script.py.mako ...  done
Generating /case2_migrate_db/migrations/env.py ...  done
Generating /case2_migrate_db/migrations/README ...  done
Generating /case2_migrate_db/migrations/alembic.ini ...  done
Please edit configuration/connection/logging settings in '/case2_migrate_db/migrations/alembic.ini' before proceeding.

创建新的迁移脚本

当你更改了数据库模型后,运行此命令来自动生成一个新的迁移脚本,例如migrations/versions/fadf2c7dd053_initial_migration.py

bash
flask db migrate -m "Initial migration"

应用迁移到数据库

这将执行最新的迁移脚本,更新数据库模式。

bash
flask db upgrade
case2_migrate_db % flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> fadf2c7dd053, Initial migration

版本升级 fadf2c7dd053-> a78c7f4db3fd

新增字段: full_name和age

python
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    # 关系
    roles = db.relationship('Role', secondary=user_roles, backref=db.backref('users', lazy='dynamic'))
    posts = db.relationship('Post', backref='author', lazy=True)
python
class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    full_name = db.Column(db.String(120))
    age = db.Column(db.Integer)
    # 关系
    roles = db.relationship('Role', secondary=user_roles, backref=db.backref('users', lazy='dynamic'))
    posts = db.relationship('Post', backref='author', lazy=True)
shell
case2_migrate_db % flask db migrate -m "Add full_name and age fields"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'users.full_name'
INFO  [alembic.autogenerate.compare] Detected added column 'users.age'
  Generating /case2_migrate_db/migrations/versions/a78c7f4db3fd_add_full_name_and_age_fields.py ...  done
shell
case2_migrate_db % flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade fadf2c7dd053 -> a78c7f4db3fd, Add full_name and age fields

回滚迁移 a78c7f4db3fd-> fadf2c7dd053

如果需要撤销最近一次或指定次数的迁移,可以使用以下命令。

bash
flask db downgrade
case2_migrate_db % flask db downgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade a78c7f4db3fd -> fadf2c7dd053, Add full_name and age fields

查看代码

https://gitee.com/PatrickW/flask-web/blob/master/src/case2_migrate_db