Skip to content

数据迁移(Flask-Migrate)

在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。

在 Flask 中可以使用 Flask-Migrate 扩展,来实现数据迁移。

Flask-Migrate 集成了 Alembic,提供了一些 flask 命令来简化迁移工作,用它来迁移数据库。并且集成到 Flask 的自定义指令中,所有操作通过 flask db 命令就能完成。

shell
pip install flask-migrate

在程序中,我们实例化 Flask-Migrate 提供的 Migrate 类,进行初始化操作:

python
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)


class Config:
    # 数据库链接配置参数
    SQLALCHEMY_DATABASE_URI = 'sqlite:///data_04.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    # SQLALCHEMY_ECHO = True
    SECRET_KEY = 'secret key'


app.config.from_object(Config)

# 创建数据库链接对象
db = SQLAlchemy()
migrate = Migrate()
db.init_app(app)
migrate.init_app(app, db)


class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(length=20))
    gender = db.Column(db.String(length=20))
    birth = db.Column(db.String(length=20))
    phone = db.Column(db.String(length=20))

    def __repr__(self):
        return '<Student %s>' % self.name

实例化 Migrate 类时,除了传入程序实例 app,还需要传入实例化 Flask- SQLAlchemy 提供的 SQLAlchemy 类创建的 db 对象作为第二个参数。

创建迁移环境

在开始迁移数据之前,需要先使用下面的命令创建一个迁移环境:

shell
flask db init

这个命令会创建 migrations 文件夹,所有迁移文件都放在里面。

注意

Flask-Migrate 提供了一个命令集,使用 db 作为命名集名称,它提供的命令都以 flask db 开头。你可以在命令行中输入 flask --help 查看所有可用的命令和说明。

迁移环境只需要创建一次。这会在你的项目根目录下创建一个 migrations 文件夹,其中包含了自动生成的配置文件和迁移版本文件夹。

生成迁移脚本

使用 migrate⼦命令可以自动生成迁移脚本:

shell
$ flask db migrate -m "init db"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'student'
Generating D:\课程-07-后端课程\02 flask框架\flaskextentions\flask-sqlalchemy\migrations\versions\0ec61111eb0d_init_db.py ...  done

这条命令可以简单理解为在 flask 里对数据库(db)进行迁移 (migrate)。-m 选项用来添加迁移备注信息。从上面的输出信息我们可以 看到,Alembic 检测出了模型的变化:表 Students 新添加了一个 timestamp 列,并且相应生成了一个迁移脚本。

迁移脚本代码

shell
"""init db

Revision ID: 0ec61111eb0d
Revises: 
Create Date: 2021-10-28 13:37:05.183723

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '0ec61111eb0d'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('student',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=20), nullable=True),
    sa.Column('gender', sa.String(length=20), nullable=True),
    sa.Column('birth', sa.String(length=20), nullable=True),
    sa.Column('phone', sa.String(length=20), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('student')
    # ### end Alembic commands ###

从上面的代码可以看出,迁移脚本主要包含了两个函数:

  • upgrade():函数把迁移中的改动应用到数据库中。
  • downgrade():函数则将改动删除。

自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成 upgrade() 和 downgrade() 函数的内容。

对比不一定完全正确,有可能会遗漏一些细节,需要进行检查

注意

就像这两个函数中的注释所说的,迁移命令是由 Alembic 自动生成的,其中可能包含错误,所以有必要在生成后检查一下。

因为每一次迁移都会生成新的迁移脚本,而且 Alembic 为每一次迁移都 生成了修订版本(revision)ID,所以数据库可以恢复到修改历史中的任一 点。正因为如此,迁移环境中的文件也要纳入版本控制。

有些复杂的操作无法实现自动迁移,这时可以使用 revision 命令手动创 建迁移脚本。这同样会生成一个迁移脚本,不过脚本中的 upgrade() 和 downgrade() 函数都是空的。你需要使用 Alembic 提供的 Operations 对象指 令在这两个函数中实现具体操作,具体可以访问 Alembic 官方文档查看。

更新数据库

生成了迁移脚本后,使用 upgrade ⼦命令即可更新数据库:

shell
$ 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  -> 0ec61111eb0d, init db

如果还没有创建数据库和表,这个命令会自动创建;如果已经创建, 则会在不损坏数据的前提下执行更新。

提示

如果你想回滚迁移,那么可以使用 downgrade 命令(降级),它会撤销 最后一次迁移在数据库中的改动,这在开发时非常有用。比如,当你执行 upgrade 命令后发现某些地方出错了,这时就可以执行 flask db downgrade 命 令进行回滚,删除对应的迁移脚本,重新生成迁移脚本后再进行更新 (upgrade)。

返回以前的版本

可以根据 history 命令找到版本号,然后传给 downgrade 命令:

shell
$ flask db history
<base> -> 0ec61111eb0d (head), init db

# 输出格式:<base> ->  版本号 (head), initial migration
  • 回滚到指定版本
shell
flask db downgrade 版本号

附录

操作顺序

  1. flask db init
  2. flask db migrate -m "版本名(注释)"
  3. flask db upgrade 然后观察表结构
  4. 根据需求修改模型
  5. flask db migrate -m "新版本名(注释)"
  6. flask db upgrade 然后观察表结构
  7. 若返回版本,则利用 flask db history查看版本号
  8. flask db downgrade(upgrade) 版本号

开发时是否需要迁移 ?

在生产环境下,当对数据库结构进行修改后,进行数据库迁移是必要的。因为你不想损坏任何数据,毕竟数据是无价的。 在生成自动迁移脚本后,执行更新之前,对迁移脚本进行检查,甚至是使用备份的数据库进行 迁移测试,都是有必要的。

而在开发环境中,你可以按需要选择是否进行数据迁移。对于大多数程序来说,我们可以在开发时使用虚拟数据生成工具来生成虚拟数据, 从而避免手动创建记录进行测试。这样每次更改表结构时,可以直接清除后重新生成,然后生成测试数据, 这要比执行一次迁移简单很多(在后面我们甚至会学习通过一条命令完成所有工作),除非生成虚拟数据耗费的时间过长。

另外,在本地开发时通常使用 SQLite 作为数据库引擎。SQLite 不⽀持 ALTER 语句,而这正是迁移工具依赖的工作机制。

也就是说,当 SQLite 数据库表的字段删除或修改后,我们没法直接使用迁移工具进行更新,你需要手动添加迁移代码来进行迁移。 在开发中,修改和删除列是很常见的行为,手动操作迁移会花费太多的时间。