语言:python
数据库:mysql
1.背景介绍
在开发产品时,经常会用到数据库,但是随着版本的迭代升级,有可能数据库中的某些字段需要进行修改,甚至加入新字段,删除老字段。上述过程就称之为数据库迁移,本文介绍如何使用sqlalchemy-migrate进行数据库迁移
2.前提准备
首先本文假定你会使用python和sqlalchemy进行mysql数据库的操作。如下是一个简单的创建表的案例
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String
from storage_grpc.utils.global_data import GlobalData
db = "mysql+pymysql://{user}:{password}@{host}:{port}/{db}".format(user=GlobalData().mysql_user,
password=GlobalData().mysql_password,
host=GlobalData().mysql_host,
port=int(
GlobalData().mysql_port),
db=GlobalData().mysql_db)
engine = create_engine(db)
Base = declarative_base(engine)
class Poo(Base):
__tablename__ = 'poo'
poo = Column(String(128), primary_key=True, unique=True)
capa = Column(String(128), nullable=True)
mou = Column(String(128), nullable=True)
ded = Column(String(128), nullable=True)
de = Column(String(128), nullable=True)
if __name__ == '__main__':
Base.metadata.create_all()
3.sqlalchemy-migrate迁移概念说明
a.首先需要建立一个“repo”(呈现为一个文件夹,作用是储存迁移过程中的各种脚本)
b.然后将“repo”和mysql数据库关联(关联之后相当于“repo”可以对mysql进行版本控制),所谓的版本控制其实就是迁移过程,每次迁移过程可以理解为一次版本升级
c.然后创建迁移脚本(命名为固定格式如:001_migra.py,下划线之前为版本号,必须是三位数,不足前面补0,下划线后面是自定义的)
d.将迁移脚本放置固定位置,执行升级(迁移)
4.开始实践,首先进行repo创建
from migrate.versioning import api
if __name__ == '__main__':
repo = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'db_repository')
if not os.path.exists(repo):
api.create(repo, 'database repository')
from migrate就是sqlalchemy-migrate库,repo其实就是路径,可以自定义,但是上述的方法可以随代码在不同地方执行而变动,很方便。调用api.create为固定用法,记住就好。 'database repository’为你的“repo”的名字,可以自定义
5.将“repo”和数据库关联
api.version_control(db, repo)
从调用的函数名能看出来,其实就是用repo对数据库进行版本控制
6.创建迁移脚本
这部相对复杂
migration = repo + '/versions/%03d_migration.py' % (
api.db_version(db, repo) + 1)
open(migration, 'wt').write(script)
首先创建文件名,migration,其中用到api.db_version来获取当前“repo”的版本号,这样在后面加1就是最新版本文件名,其中%03d表示3位数,不足补0
然后创建script脚本,将其写入migration。script创建如下
old_model = api.create_model(db, repo)
import types
new = types.ModuleType('old_model')
exec(old_model, new.__dict__)
script = api.make_update_script_for_model(db, repo, new.meta,
Base.metadata)
print(script)
这里使用了一个函数api.make_update_script_for_model,这个函数通过导入旧模块new.meta和新模块Base.metadata来创建升级脚本。模块可以理解为数据库的结构。其中旧模块是通过api.create_model获取当前数据库的结构,新模块其实就是你代码中当前的结构Base.metadata。函数api.make_update_script_for_model通过对比两者的差异自动生成脚本script,你可以打印出来看一下便于理解。
然后你就获得了migration文件,并放在了你的“repo”里面
7.执行升级
api.upgrade(db, repo)
如果你的数据库的结构和代码中的不一致,执行之后,就会自动升级了
8.总结
本文借鉴了很多其他的相关文章,但是由于其中代码太老,有很多错误,就不贴链接误导大家了。整体代码放在最后,仅供参考
提醒:修改表不是随便修改的,例如元素的类型不能修改,名字可以修改,元素可以新增可以删除,好像primary_key也不能增加。如果执行upgrade时报错,多半是修改的内容有问题
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String
from storage_grpc.utils.global_data import GlobalData
import os
from migrate.versioning import api
db = "mysql+pymysql://{user}:{password}@{host}:{port}/{db}".format(user=GlobalData().mysql_user,
password=GlobalData().mysql_password,
host=GlobalData().mysql_host,
port=int(
GlobalData().mysql_port),
db=GlobalData().mysql_db)
engine = create_engine(db)
Base = declarative_base(engine)
class Poo(Base):
__tablename__ = 'poo'
poo = Column(String(128), primary_key=True, unique=True)
capa = Column(String(128), nullable=True)
mou = Column(String(128), nullable=True)
ded = Column(String(128), nullable=True)
de = Column(String(128), nullable=True)
if __name__ == '__main__':
Base.metadata.create_all()
repo = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'db_repository')
if not os.path.exists(repo):
api.create(repo, 'database repository')
api.version_control(db, repo)
migration = repo + '/versions/%03d_migration.py' % (
api.db_version(db, repo) + 1)
old_model = api.create_model(db, repo)
import types
new = types.ModuleType('old_model')
exec(old_model, new.__dict__)
script = api.make_update_script_for_model(db, repo, new.meta,
Base.metadata)
print(script)
open(migration, 'wt').write(script)
api.upgrade(db, repo)