用Alembic进行数据库版本管理

用SQLAlchemy进行数据库开发是很方便,特别是它有强大的ORM,只要在model里把数据库模型定义好,直接初始化一下就行了。

但是实际的开发不可能一开始就把数据库定义得这么完善,另外,也有时会碰到先有数据库再进行开发的情况,这时就比较麻烦了。

所以需要有工具支持。

Alembic就是一个配合SQLAlchemy的数据库管理工具。

在介绍Alembic之前,先来看看已经有数据库时要怎么办。

已有数据库

如果你之前已经用过SQLAlchemy,更常见的情况是实际的数据库经过一段时间的修改已经与早期定义的models不一致。不论是之前已有数据库,还没创建models,还是已有models,但与数据库不一致,都可以如下处理。

这就需要另外一个工具:sqlacodegen。安装的话就是一句PIP的事情:

pip install sqlacodegen

然后用命令(以Mysql为例,pg的话就用psycopg2):

sqlacodegen mysql+pymysql://user:pass@localhost:3306/dbname --outfile db/models.py

即可从数据库生成一个SQLAlchemy的models定义文件。有了这个保持一致的models才好用alembic来管理。

初始化

安装就不说了,也是一句PIP的事情。

然后在项目路径下执行:

alembic init alembic

会创建几个文件和目录:alembic.ini,alembic目录及其下面的env.py, README, script.py.mako和versions目录。

最简单的用法就是在alembic.ini里找到sqlalchemy.url参数,改为当前开发环境的数据库连接。然后修改alembic/env.py

from db.models import Base  # 从你的sqlalchemy的models定义中导入Base

# ...

target_metadata = Base.metadata  # 把原来默认的None改为你的Base.metadata,以便alembic可以找到你的数据库模型定义

然后执行:

alembic revision --autogenerate -m "init"

即可以versions目录下生成一个版本文件。

如果数据库不存在,则这个版本可以作为初始化的建库基础。如果数据库已存在,那models一定要与现在的数据库保持一致,最好是用sqlacodegen从数据库重新生成一个models。

然后运行:

alembic upgrade head

如果数据库是新的空库,这个操作就会根据models的定义创建一个数据库结构。如果数据库已存在并且与models定义一致,即可将开发环境的数据库的alembic版本标记为当前版本,以便后续变更。

之后部署到正式环境,只需要配置好正式环境的数据库链接,然后运行alembic upgrade head,即可创建一个和开发环境一致的数据库。

版本升级

随着开发工作的进行,数据库肯定也会变更,这个时候要注意,不要直接在开发数据库上变更,所有的变更都要在sqlalchemy的models上进行。在更新了models以后,执行:

alembic revision --autogenerate -m "new feature"

生成一个new feature的alembic新版本记录。同样在生成以后alembic upgrade head更新一下开发环境的数据库版本。这样数据库就与models一致了,可以继续进行开发。

部署到正式环境后,同样执行一下alembic upgrade head,即可将正式环境数据库更新到和开发环境一致,这时再部署代码运行就不会有数据库不一致的问题了。

关于配置

修改alembic.ini里的数据库链接固然方便,但是不便于与生产环境同步,所以最好还是动态读取实际的配置。

假设配置信息放在一个单独的配置文件config.py里,生产环境和开发环境用的配置文件是不一样的,代码里通过一个Config类去读取,类似这样:

class Config:
    config: dict = load_config()  # 从配置文件里读取配置
    DB_URL: str = config.get("DB_URL")  # 读取数据库配置
    
settings = Config()

那么我们现在就可以来修改alembic的配置,让它去读取实际的配置,而不是写死在alembbic.ini里,这样不论是在开发环境还是在生产环境运行alembic,使用的都是正确的数据库配置。

首先修改alembic.ini,把sqlalchemy.url一项注释掉,然后修改alembic/env.py,让它改为读取项目配置,而不是alembic.ini:

# 导入配置
from config import settings

# ...

# 修改run_migrations_offline函数
def run_migrations_offline() -> None:
    # url = config.get_main_option("sqlalchemy.url")  # 注释掉原来读取alembic.ini的语句
    url = settings.DB_URI  # 改为读取项目配置
    
    # ...
    
# ...

# 修改run_migrations_online函数
def run_migrations_online() -> None:
    # 读取项目配置并设置到alembic的sqlalchemy配置里
    url = settings.DB_URI
    config.set_main_option("sqlalchemy.url", url)
    
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    # ...
    
#...

这样就可以方便地在开发环境和生产环境之间同步了。

记得每次开发环境有修改过数据库模型,就创建一个alembic版本。

——注意,只能在models.py里修改,不得直接修改数据库

提交代码的时候把alembic版本一起提交上去,在正式环境上部署时同样要把变更的alembic版本更新下来,然后运行alembic upgrade head同步生产环境的数据库结构,这样就能保持一致了。

推送到[go4pro.org]