背景

在很多时候,在我们设计好模型之后,又需要进行一定的改变和更新数据操作;最直接的方式就是删除原来的旧表,但我们之前的数据也会删除;究其原因是我们不能精确记住每个修改和对应DDL,这时候就需要数据库迁移框架了。

对于Django ,框架内部就集成了数据库迁移模块,我们可以很方便使用

  • 回顾Django迁移命令:
基本语法:
python manage.py xx [app_name]

xx如下
migrate,负责应用和撤销迁移。
makemigrations,基于模型的修改创建迁移。
sqlmigrate,展示迁移使用的 SQL 语句。
showmigrations,列出项目的迁移和迁移的状态。
  • Django迁移过程分为3步走:
1:修改models数据模型
2:生成迁移文件(在migrations/文件夹下)python manage.py makemigrations
3:向数据库执行迁移操作:python manage.py migrate
  • 总结,迁移数据库的主要思路:
1:修改数据模型
2:生成新迁移文件
3:执行迁移

那么在Flask + SQLAlchemy下,如何选择数据库管理迁移工具?

Alembic介绍

AlembicSqlalchemy的作者就已经实现的一个数据库版本化管理工具:

Alembic来帮助我们实现数据库的迁移,数据库迁移工具可以在不破坏数据的情况下更新数据库表的结构。蒸馏器(Alembic)是炼金术士最重要的工具,要学习SQL炼金术(SQLAlchemy),当然要掌握蒸馏器的使用。

Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。

快速上手

# 安装alembic
pip install alembic

# 初次使用时,需要初始化,创建迁移环境和 alembic.ini 文件、env.py文件
alembic init migrations

# 修改alembic.ini配置,改变 sqlalchemy.url 值,配置数据库连接。
sqlalchemy.url = mysql+pymysql://root:mysql@localhost/ocean_209_old
    
# 生成数据库迁移文件。
# --autogrenerate 选项参数来自动生成迁移脚本(这里演示使用手动:仅生成空模板)
alembic revision -m "first comment"

# 同步数据库
alembic upgrade head

Alembic 实践

1、创建 Alembic 迁移环境

在使用 Alembic 之前需要先建立一个 Alembic 脚本环境,通过在工程目录下输入

# 切换到项目根目录下
alembic init alembic
# init之后的alembic指的是保存迁移文件的目录名称。
# 执行之后会在项目根目录下生成 alembic.ini文件 和指定名称的迁移文档保存目录()。

命令可以快速在应用程序中建立 Alembic 脚本环境,命令行看到以下输出:

$ alembic init alembic
Creating directory C:\Projects\ocean\alembic ...  done
Creating directory C:\Projects\ocean\alembic\versions ...  done
Generating C:\Projects\ocean\alembic.ini ...  done
Generating C:\Projects\ocean\alembic\env.py ...  done
Generating C:\Projects\ocean\alembic\README ...  done
Generating C:\Projects\ocean\alembic\script.py.mako ...  done
Please edit configuration/connection/logging settings in 'C:\\Projects\\ocean\\alembic.ini' before proceeding.

alembic生成的目录和文件如下:

alembic
├── README
├── env.py
├── script.py.mako
└── versions/
alembic.ini

alembic 目录:迁移脚本的根目录,放置在应用程序的根目录下,可以设置为任意名称。在多数据库应用程序可以为每个数据库单独设置一个 Alembic 脚本目录。

  • versions 目录:用于存放各个版本的迁移脚本。初始情况下为空目录,通过 revision 命令可以生成新的迁移脚本。
  • README 文件:说明文件,初始化完成时没有什么意义。
  • env.py 文件:用来配置和实例化 SQLAlchemy 引擎,提供连接引擎并进行事务,保证当 Alembic 执行命令时被合理的调用。
  • script.py.mako 文件:模板文件,定义生成迁移文件的基本模板。(我们可以在这里添加一些代码)

alembic.ini文件:在开始任何的操作之前需要先配置该文件中的 sqlalchemy.url 指向你自己的数据库地址。

2、修改配置

修改 alembic.ini

配置所使用的数据库连接,即需要迁移的数据库地址

# mysql
sqlalchemy.url = mysql+pymysql://fdz:[email protected]:3333/ocean_209_old

# postgres
sqlalchemy.url = postgresql+psycopg2://root:[email protected]/table_name

修改 env.py

为了让alembic追踪模型信息,与ini相比较,需要将 target_metadata 赋值为要追踪的 metadata

根据需要,选择一个表来源,将target_metadata = None 指向其中一个metadata

"""配置target_metadata"""
# 来源一:Model Base
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
from ocean.model._base import Base  # 不同代码结构不同
metadata = Base.metadata  # # 从任意一个我们的model可以拿到总的Base
# # 来源二:已存在表reflect
from sqlalchemy import create_engine
from sqlalchemy.schema import MetaData

engine = create_engine('mysql+pymysql://root:mysql@localhost/ocean_253_test_alembic')
metadata = MetaData(engine)
# # only参数可传入指定的数据库表名列表,表示只对这些表生成迁移脚本,否则就对DB中的全表生成
# metadata.reflect(only=["users"])
metadata.reflect()  # 不会返回任何值
# # 以上两种方式根据需要选择其中一种即可
target_metadata = metadata

3、自动生成迁移/版本文件

在 Alembic 中通过 revision 子命令的 –autogrenerate 选项参数来生成自动迁移文件:

# --autogrenerate 选项参数来自动生成迁移脚本
# -m 增加版本信息
alembic revision --autogenerate -m "gen_ddl_by_reflect"

命令返回:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'incomplete'
INFO  [alembic.autogenerate.compare] Detected added table 'regions'
INFO  [alembic.autogenerate.compare] Detected added table 'sms_push_history'
INFO  [alembic.autogenerate.compare] Detected added table 'template'
INFO  [alembic.autogenerate.compare] Detected added table 'voice_failure'
INFO  [alembic.autogenerate.compare] Detected added table 'voice_push_history'
INFO  [alembic.autogenerate.compare] Detected added table 'voice_report'
INFO  [alembic.autogenerate.compare] Detected added table 'whitelist_batch'
INFO  [alembic.autogenerate.compare] Detected NULL on column 'alarm_level_policy.create_at'
INFO  [alembic.autogenerate.compare] Detected NULL on column 'user_login_event.current'
INFO  [alembic.autogenerate.compare] Detected added column 'users.mobile'
Generating C:\Projects\ocean\alembic\versions\daf4b972f41d_gen_ddl_by_reflect.py ...  done

可以看到alembic/versions/文件夹下生成了一个py文件,里面有两个函数用于升级数据库和降级数据库,可根据需要修改。

了解可以检测的model改动

自动生成模式可以检测model改动变化是局限的

模式元素支持的更改无法检测的改动
添加和删除名称的变化
添加、删除、列的“允许空值”状态的变化名称的变化
索引索引的基本变化、显示命名的唯一性约束、支持自动生成索引和唯一性约束
基本的重命名
约束——无明确名称的约束
类型——ENUM这类数据库后端不直接支持的类型

查看生成的迁移/版本文件

迁移文件中制定了当前版本号 revision 和父版本号 down_revision ,以及相应的升级操作函数 upgrade 和降级操作函数 dwongrade。在 upgradedwongrade 函数中通过相应的 API 来操作 op 和 sa 对象来完成对数据库的修改。

举例,生成的 XXXXversion_comment.py 迁移文件内容:

# 主要是两大操作函数:分别对应了升级和降级操作
def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('supplier_attach')
    op.add_column('users', sa.Column('test_add_col', mysql.CHAR(length=11), nullable=True, comment='测试增加字段'))
    op.alter_column('whitelist_batch', 'uid',
               existing_type=mysql.INTEGER(),
               nullable=False)
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('whitelist_batch', 'uid',
               existing_type=mysql.INTEGER(),
               nullable=True)
    op.drop_column('users', 'test_add_col')
    op.create_table('supplier_attach',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('supplier', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('filename', mysql.CHAR(charset='utf8', collation='utf8_general_ci', length=255), nullable=False),
    sa.Column('raw_filename', mysql.CHAR(charset='utf8', collation='utf8_general_ci', length=255), nullable=False),
    sa.Column('create_at', mysql.TIMESTAMP(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8',
    mysql_engine='InnoDB',
    mysql_row_format='DYNAMIC'
    )
    # ### end Alembic commands ###

重要提示

在进行下一步同步之前,建议先检查自动生成的迁移文件是否如预想一样,否则可能导致表或原字段被删除;

例如:最常见的是如果你只是想修改某一列的名字,它是检测不出来的,它会将你以前的列删除再添加一个新的列。

4、同步到数据库

alembic upgrade head

命令返回:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> daf4b972f41d, gen_ddl_by_reflect

查看数据库,除了迁移产生的新表,还有一个alembic字典建立的表叫做alembic_version,只有一个字段和一个值version_num,记录了当前的数据库版本。

其它命令

#查看线上数据库处于什么版本
alembic current
#查看项目目录中的迁移脚本信息(当前head)
alembic history

#更新数据库
alembic upgrade 版本号
#更新到最新版
alembic upgrade head
#降级数据库
alembic downgrade 版本号
#降级到最初版
alembic downgrade head
#离线更新(生成sql)
alembic upgrade 版本号 --sql > migration.sql
#从特定起始版本生成sql
alembic upgrade 版本一:版本二 --sql >migration.sql
    
#清除所有生成版本
将version删除, 并删除alembic_version表

报错解决

FAILED: Target database is not up to date.

只修改了model,从没同步数据库,数据库没有记录版本信息,使用alembic stamp head 更新版本记录 另外就是只简单修改了model字段的类型长度等,alembic默认不检测,需要配置compare_type

FAILED: Can’t locate revision identified by ‘1e065e4399bf’

删除数据库中生成的alembic_version表,重新迁移

[SQL: ALTER TABLE whitelist_batch MODIFY uid INTEGER NOT NULL]

同步数据库时,SQL报错,考虑模型定义约束和实际数据库存储不一致,例如这里模型定义nullable=False,实际数据库uid字段却存在null;

Warning: (3719, “‘utf8’ is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous.”) 原理解释:MySQL 中的 utf8 就是 utf8mb3,最大兼容三字节的 unicode 字符,MySQL 在 5.5.3 版本之后增加了 utf8mb4 的编码,mb4 就是 most bytes 4 的意思,专门用来兼容四字节的 unicode 字符,utf8mb4 是 utf8mb3 的超集,utf8mb3 和 utf8mb4 表示的范围如下表:

说明mysql utf8 / utf8mb3mysql utf8mb4
max bit34
范围基本多文种平面 + US-ASCII辅助平面(Supplementary) + 基本多文种平面 + US-ASCII
unicode范围U+0000 - U+FFFFU+0000 - U+10FFFFF
常见字符英文字母,CJK大部分常用字等CJK非常用字,数学符号,emoji表情等

Flask-Migrate介绍

Flask-Migrate 的用法和 Alembic 类似,只是将 alembic 换成了你的应用名称「或 flask」+ db 的方式。

Django ORM VS Alembic

  • Django ORM

默认migration比较完全自动化,能精确检测model变化, 例如修改一个max_length,都能准确帮助修改数据库

  • Alembic

默认只能检测基本的更改:What does Autogenerate Detect (and what does it not detect?)

参考

https://blog.51cto.com/u_3409716/2904146

https://cloud.tencent.com/developer/article/1536292

https://blog.csdn.net/qq_41637554/article/details/82625598

https://www.cnblogs.com/snail-z/p/14199039.html