在我之前使用 Flask 实现简单接口时,为了方便,我每次都会将数据表删除掉,然后重新创建表和添加数据。因为测试数据只有几条,所以可以使用删表重建的方式,但在实际的项目中,是不可能使用这种方式的,删表意味着删数据。
在开发过程中,有时候需要修改数据库模型,比如新功能需要增加一个字段,在 Flask 代码中修改模型类后,要将新增的字段同步到数据库中。这时候是不能删表重建的。
在 Flask 中,可以使用数据库迁移来解决这个问题,数据库迁移可以追踪数据模型类的变化,然后把变动应用到数据库中,不会删表造成数据丢失。
一、安装 Flask-Migrate 和 Flask-Script
在 Flask 中使用 Flask-Migrate 扩展,来实现数据迁移。
Flask-Migrate 的文档路径:https://flask-migrate.readthedocs.io/en/latest/ ,内容不多,可以看看。
先安装 Flask-Migrate 。
pip install Flask-Migrate
执行安装命令,会自动下载和安装 Flask-Migrate 模块及相关的依赖库。
其中一个非常重要的依赖库是 Alembic ,数据库迁移时自动生成迁移文件和迁移脚本都是 Alembic 完成的,也是因为 Alembic 的机制,数据库迁移操作只能在 Linux 系统中使用,不能在 Windows 中使用。
具体可以仔细研究一下 Alembic ,文档路径:https://alembic.sqlalchemy.org/en/latest/tutorial.html 。
另外,需要用到 Flask-Script 模块,使用 Flask-Script 来管理 Flask 应用程序 app ,Flask 程序中的操作可以通过命令来完成。
Flask-Migrate 提供了一个 MigrateCommand 类,将这个类添加到 Flask-Script 的 Manager 对象中,可以更方便地使用命令来进行数据库迁移,Flask-Migrate 文档中也是推荐这种方式的。
所以,也要安装 Flask-Script 。
pip install Flask-Script
二、准备数据库迁移的模型类
在项目文件夹下创建一个 flask_migrate_db.py 文件,注意文件名不要叫 flask_migrate.py ,否则会与 Flask-Migrate 中的文件名冲突,导包时就会报错。
先在 flask_migrate_db.py 中编写如下代码,做好数据库迁移的准备。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
app = Flask(__name__)
manager = Manager(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://admin:Mysql!123@127.0.0.1:3306/MyDB_two'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = True
app.config['SECRET_KEY'] = 'NFAIOSDFHASOGHAOSPIGAOWE'
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
class Computer(db.Model):
__tablename__ = 'Computer_tb'
cid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32))
# price = db.Column(db.Integer)
# color = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('User_tb.uid'))
def __repr__(self):
return 'Computer_name: {}'.format(self.name)
class User(db.Model):
__tablename__ = 'User_tb'
uid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
phones = db.relationship('Computer', backref='user', lazy='dynamic')
def __repr__(self):
return 'User_name: {}'.format(self.name)
@app.route('/')
def index():
return 'Hello Migrate!'
if __name__ == '__main__':
# user_one = User(name='You')
# db.session.add(user_one)
# computer_one = Computer(name='Dell', user_id=1)
# db.session.add(computer_one)
# db.session.commit()
manager.run()
代码中连接了 mysql 数据库 MyDB_two,这个数据库需要提前创建好。将 Flask 应用对象 app 和数据库连接对象 db 传入 Migrate ,创建迁移对象 migrate 。将 MigrateCommand 添加到 Flask-Script 的 Manager 中,最后使用 Manager 管理和运行 app 。
代码中定义了两个数据库模型类 Computer 和 User ,执行数据库迁移后会在 MyDB_two 中创建两张表 Computer_tb 和 User_tb 。
三、执行数据库迁移
1. 初始化迁移仓库
python flask_migrate_db.py db init
在项目目录下执行 init 初始化命令,会自动在项目目录下创建一个 migrations 目录。这个目录是 Alembic 模块自动创建的,默认名字叫 migrations ,可以在创建 migrate = Migrate(app, db) 对象时传入 directory='filename' 参数自定义名字。
migrations 里面有一个 versions 文件夹,这个文件夹用于存放迁移脚本,执行迁移命令后会自动生成迁移脚本保存在里面。
env.py 是迁移环境的相关信息。
数据库迁移时,初始化命令只需要执行一次,如果在一开始发现执行有问题,需要重新初始化,要先删除 migrations 目录才行。如果已经执行迁移命令,需要重新初始化,要先删除 migrations 目录和到数据库中删除 alembic_version 表。
2. 创建迁移脚本
python flask_migrate_db.py db migrate -m "create table"
继续执行 migrate 命令生成迁移脚本,通过 -m 参数添加迁移信息,这类似于 git 提交代码时添加提交信息。
执行命令后,会在 versions 目录下生成一个迁移脚本,迁移脚本的名字是版本 id 和迁移信息拼接的结果,打开迁移脚本,脚本里定义了一个 upgrade() 函数,函数里的代码就是创建数据表的代码,感兴趣可以打开看看。
每次数据模型类有变化,需要迁移数据库时,都需要执行创建迁移脚本的命令,生成新版本的迁移脚本。
3. 执行数据库迁移
python flask_migrate_db.py db upgrade
生成迁移脚本后,数据库还没有变化,迁移结果还没有生效,需要继续执行 upgrade 命令,使迁移结果应用到数据库中。执行 upgrade 命令相当于执行迁移脚本中的 upgrade() 函数,执行里面创建表的代码。
执行 upgrade 命令后,会在数据库中创建一张 alembic_version 表,这张表不是代码中定义的,是 Alembic 自动创建的(看名字就知道了),里面保存的是当前数据库的版本 id ,alembic_version 表不能删除,删除后就不能继续执行数据库迁移操作了,除非重新初始化。
同时,执行 upgrade 命令后,会根据代码中定义的模型类创建对应的表,表的字段与模型类中定义的一致。
如果数据库中有其他表(没有对应模型类的表),会被删除。这点需要特别注意,数据库迁移时最好使用一个新的数据库(不要与其他项目用同一个数据库),避免造成数据丢失。
每次生成迁移脚本后,都需要执行 upgrade 命令,迁移结果才会生效。
4. 添加数据和添加字段
现在已经执行了第一次数据库迁移,数据库中创建了对应的表,但是表都是空的,没有数据。
添加下面的代码,然后 python flask_migrate_db.py 运行代码,在两张表里面各添加一条数据。
user_one = User(name='You')
db.session.add(user_one)
computer_one = Computer(name='Dell', user_id=1)
db.session.add(computer_one)
db.session.commit()
添加后,到数据库中查询,确认数据添加成功,数据库已经可以正常使用了。
现在,假设需要修改数据库模型类,如需要在模型类 Computer 中增加一个 price 字段。先注释掉添加数据的代码(有唯一字段不能重复运行和添加),然后在 Computer 类中添加一个字段。
price = db.Column(db.Integer)
5. 生成新版本数据库迁移脚本
python flask_migrate_db.py db migrate -m "add price to computer"
模型类的代码修改后,数据表并没有变化,需要重新生成迁移脚本和执行数据库迁移。
执行命令后,会生成一个新的迁移脚本,打开新的迁移脚本,脚本里面的代码就是执行数据库新增字段的代码。
前面提到,在 Linux 系统中可以顺利执行数据库迁移,在 Windows 中会失败,是因为 Alembic 生成迁移脚本的机制,现在就简单解释一下原因。
执行上面相同的操作后,打开迁移脚本,Linux 系统和 Windows 系统中生成的迁移脚本代码是不同的。Linux 中的脚本代码是直接添加字段,Windows 中的脚本代码包含了删除关系字段、删除表和重新创建表的代码,而且顺序是乱的(创建在前删除在后,删除表的顺序也不对),所以在 Windows 中执行迁移时会失败。
如果要在 Windows 中成功执行数据库迁移,就不能直接使用 Alembic 生成的迁移脚本,需要自己修改迁移脚本。可以调整迁移脚本中代码的顺序,先删除关系字段,关系表,然后删除其他表,最后创建新表,这样执行迁移后,相当于删表重建,表结构修改成功了,但是数据丢失了。更好的方法是将代码改成增加字段的代码(与Linux中的一样),再执行迁移,就能完成修改表结构并保留数据。
所以,要在 Windows 中执行数据库迁移,要知道怎么改迁移脚本(与模型类变化一致,改时要细心)。
6. 执行迁移
python flask_migrate_db.py db upgrade
最后执行 upgrade 命令使迁移脚本生效。
执行之后,成功在 Computer_tb 表中增加了字段 price 。
再查询一下数据,看数据是否丢失。
可以看到,数据都保留着,之前的数据没有新字段的值,默认为空 NULL 。
7. 其他操作
在所有迁移脚本中,除了 upgrade() 函数外,还有一个 downgrade() 函数,这两个函数里面的代码是相反的,downgrade() 函数是用于回退数据库迁移的。
upgrade() 函数把迁移中的改动应用到数据库中,downgrade() 函数则将改动删除。
python flask_migrate_db.py db downgrade
执行 downgrade 命令如果不指定版本 id ,默认是回退到上一个版本,即版本 -1 (减一),也可以指定版本回退。
对数据库迁移后,可以使用 history 命令找到历史的版本号和变更过程。
python flask_migrate_db.py db history
此外,数据库迁移时还可以指定参数来完成更多的功能,如初始化时指定 --multi 参数可以实现多数据库迁移(同时使用多种数据库,如 mysql + postgresql ),生成迁移脚本和执行迁移时使用 --sql 参数可以查看数据库迁移命令对应的原生 SQL 语句。具体使用方法可以去查看 Flask-Migrate 的文档。