前言

通常我们会将我们的代码放入到某个VCS(版本控制系统)中,进行可追溯的版本管理。一个项目除了代码,通常还会有一个数据库,这个数据库可能会随着项目的演进发生变化,甚至需要可以回滚到过去的某个状态,于是一些工具将数据库的版本化也纳入了管理。

AlembicSqlalchemy的作者实现的一个数据库版本化管理工具,它可以对基于Sqlalchemy的Model与数据库之间的历史关系进行版本化的维护。

开始

Auto Generating Migrations

安装

  • pip install alembic

初始化

  • alembic init alembic

执行后会在工程目录下生成以下的目录结构:

.
├── alembic
│   ├── env.py            # 配置DataBase.metadata
│   ├── README
│   ├── script.py.mako    # 迁移脚本生成模版
│   └── versions          # 版本更新(迁移脚本)的内容目录
└── alembic.ini           # 配置数据库

数据库模板类

# ./database.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://user_name:password@localhost:3306/database_name'
db = SQLAlchemy(app)...emmmm...

配置

  • alembic.ini

    • sqlalchemy.url = driver://user:pass@localhost/dbname
    • mysql+mysqldb://user_name:password@localhost:3306/database_name
  • alembic/env.py
    • target_metadata = None为以下内容:
import sys
from database import db              # 这个路径要改为数据库模板类所在的目录下
# dirname(dirname(abspath(__file__))) -> ./alembic
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
from db import dbtarget_metadata = db.metadata

使用

这东西好处就是可以像Git一样升级和降级数据库。

  • 这个工具是按照数据库来判断你是否要对数据库进行升级。因此,参照物是数据库表,而变化的是你写的代码。

创建数据库版本[手写]

  • alembic revision -m "注释"

    • -m:注释
    • 缺点:手写……

跑完之后会在./alembic/versions生成脚本。

alembic
├── env.py
├── README
├── script.py.mako
└── versions└── 6f406f586bbb_注释.py

脚本内容如下所示:

"""注释Revision ID: 6f406f586bbb
Revises: 8b6caa4ac725
Create Date: 2019-05-14 20:06:39.940623"""
from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = '6f406f586bbb'
down_revision = '8b6caa4ac725'
branch_labels = None
depends_on = Nonedef upgrade():passdef downgrade():pass

创建一个新表

Operations的文档:Operation Reference


...省略...def upgrade():op.create_table('account',sa.Column('id', sa.Integer, primary_key=True),sa.Column('name', sa.String(50), nullable=False),sa.Column('description', sa.Unicode(200)),)def downgrade():op.drop_table('account')

修改表

"""Add a columnRevision ID: ae1027a6acf
Revises: 1975ea83b712
Create Date: 2011-11-08 12:37:36.714947"""# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'from alembic import op
import sqlalchemy as sadef upgrade():op.add_column('account', sa.Column('last_transaction_date', sa.DateTime))def downgrade():op.drop_column('account', 'last_transaction_date')

总而言之,你可以用Alembic来创建和修改表的结构。

创建数据库版本[自动]

  • alembic revision --autogenerate -m "initdb"

    • 自动生成数据库版本
    • 说明:当且仅当数据库为空的时候才会自动根据创建的Model创建version版本。

没有数据库表

没有数据库的时候,Alembic会根据模型创建,命令行执行的结果:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table '...'
INFO  [alembic.autogenerate.compare] Detected added table '...'
INFO  [alembic.autogenerate.compare] Detected added table '...'
INFO  [alembic.autogenerate.compare] Detected added table '...'
INFO  [alembic.autogenerate.compare] Detected added table '...'Generating /.../project_name/alembic/versions/5e2d2560b497_init_db.py ... done

看看文件内容:

"""init dbRevision ID: 5e2d2560b497
Revises:
Create Date: 2019-05-15 11:11:14.684351"""
from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = '5e2d2560b497'
down_revision = None
branch_labels = None
depends_on = Nonedef upgrade():# ### commands auto generated by Alembic - please adjust! ###op.create_table('...',sa.Column('id', sa.Integer(), nullable=False),sa.Column('...', sa.String(length=50), nullable=False),sa.Column('...', sa.DateTime(), nullable=True),sa.Column('...', sa.DateTime(), nullable=True),sa.PrimaryKeyConstraint('id'))......op.create_table('...',sa.Column('id', sa.Integer(), nullable=False),sa.Column('...', sa.String(length=50), nullable=False),sa.Column('...', sa.DateTime(), nullable=True),sa.Column('...', sa.DateTime(), nullable=True),sa.PrimaryKeyConstraint('id'))# ### end Alembic commands ###def downgrade():# ### commands auto generated by Alembic - please adjust! ###op.drop_table('...')......op.drop_table('...')# ### end Alembic commands ###

有数据库表

执行后会在./alembic/versions生成脚本。

alembic
├── env.py
├── README
├── script.py.mako
└── versions└── 8b6caa4ac725_init_db.py

脚本内容如下所示:

"""init dbRevision ID: 8b6caa4ac725
Revises:
Create Date: 2019-05-14 20:02:46.802136"""
from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = '8b6caa4ac725'
down_revision = None
branch_labels = None
depends_on = Nonedef upgrade():# ### commands auto generated by Alembic - please adjust! ###...这里我省略了,都是对表修改操作...# ### end Alembic commands ###def downgrade():# ### commands auto generated by Alembic - please adjust! ###...这里我省略了,都是对表修改操作...# ### end Alembic commands ###

自动写完了代码,优秀!╮(╯▽╰)╭

有表和没表的区别

Alembic是根据数据库与代码中的Model的定义进行比对,来生成对应的版本号的。(Model可以看下一张的示例)

要是创建了数据库表,而当前的Model并没有任何修改,提交之后upgradedowngrade是为空的。

加入没有创建数据库表,那么就根据Model中的将创建表和删除表的操作分别在upgradedowngrade中编写出来。

示例

Model定义:

class Test(db.Model):id = db.Column(db.Integer, primary_key=True)test1 = db.Column(db.String(50), unique=False, nullable=False)test2 = db.Column(db.String(120), unique=True, nullable=False)create_at = db.Column(db.DateTime, unique=False, nullable=True, default=datetime.utcnow)update_at = db.Column(db.DateTime, unique=False, nullable=True, default=datetime.utcnow)
  • 初始化:alembic init alembic
  • 配置(参照前几章)
  • 初始化第一个版本alembic revision --autogenerate -m "init db"此时数据库表没有创建
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'test'Generating /.../project_name/alembic/versions/3b63c8e4c302_init_db.py ... done
  • 文件./alembic/versions/3b63c8e4c302_init_db.py
"""init dbRevision ID: 3b63c8e4c302
Revises:
Create Date: 2019-05-15 11:48:45.805342"""
from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = '3b63c8e4c302'
down_revision = None
branch_labels = None
depends_on = Nonedef upgrade():# ### commands auto generated by Alembic - please adjust! ###op.create_table('test',sa.Column('id', sa.Integer(), nullable=False),sa.Column('test1', sa.String(length=50), nullable=False),sa.Column('test2', sa.String(length=120), nullable=False),sa.Column('create_at', sa.DateTime(), nullable=True),sa.Column('update_at', sa.DateTime(), nullable=True),sa.PrimaryKeyConstraint('id'),sa.UniqueConstraint('test2'))# ### end Alembic commands ###def downgrade():# ### commands auto generated by Alembic - please adjust! ###op.drop_table('test')# ### end Alembic commands ###
  • 查看版本:alembic history

    • <base> -> 3b63c8e4c302 (head), init db
  • 数据库升级:alembic upgrade 3b63c8
Database changed
mysql> show tables;
+------------------+
| Tables_in_qmasdb |
+------------------+
| alembic_version  |
| test             |
+------------------+
2 rows in set (0.00 sec)mysql> desc test;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(11)      | NO   | PRI | NULL    | auto_increment |
| test1     | varchar(50)  | NO   |     | NULL    |                |
| test2     | varchar(120) | NO   | UNI | NULL    |                |
| create_at | datetime     | YES  |     | NULL    |                |
| update_at | datetime     | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
  • 增加一个hahahahahahahat字段:
class Test(db.Model):id = db.Column(db.Integer, primary_key=True)test1 = db.Column(db.String(50), unique=False, nullable=False)test2 = db.Column(db.String(120), unique=True, nullable=False)hahahahahahahat = db.Column(db.String(120), unique=True, nullable=False) # ╮(╯▽╰)╭create_at = db.Column(db.DateTime, unique=False, nullable=True, default=datetime.utcnow)update_at = db.Column(db.DateTime, unique=False, nullable=True, default=datetime.utcnow)
  • 提交更新:alembic revision --autogenerate -m "add hahahahahahahat."
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'test.hahahahahahahat'
INFO  [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['hahahahahahahat']'Generating /.../project_name/alembic/versions/290c92587e1a_add_hahahahahahahat.py ... done
  • 让我们看看./alembic/versions/290c92587e1a_add_hahahahahahahat.py这个文件。
"""add hahahahahahahat.Revision ID: 290c92587e1a
Revises: 3b63c8e4c302
Create Date: 2019-05-15 11:53:28.251516"""
from alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = '290c92587e1a'
down_revision = '3b63c8e4c302'
branch_labels = None
depends_on = Nonedef upgrade():# ### commands auto generated by Alembic - please adjust! ###op.add_column('test', sa.Column('hahahahahahahat', sa.String(length=120), nullable=False))op.create_unique_constraint(None, 'test', ['hahahahahahahat'])# ### end Alembic commands ###def downgrade():# ### commands auto generated by Alembic - please adjust! ###op.drop_constraint(None, 'test', type_='unique')op.drop_column('test', 'hahahahahahahat')# ### end Alembic commands ###
  • 看下提交记录:alembic history
3b63c8e4c302 -> 290c92587e1a (head), add hahahahahahahat.
<base> -> 3b63c8e4c302, init db
  • 升级数据库:alembic upgrade 290c92
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 3b63c8e4c302 -> 290c92587e1a, add hahahahahahahat.
  • 看看数据库
mysql> desc test;
+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | int(11)      | NO   | PRI | NULL    | auto_increment |
| test1           | varchar(50)  | NO   |     | NULL    |                |
| test2           | varchar(120) | NO   | UNI | NULL    |                |
| create_at       | datetime     | YES  |     | NULL    |                |
| update_at       | datetime     | YES  |     | NULL    |                |
| hahahahahahahat | varchar(120) | NO   | UNI | NULL    |                | # 参数添加进来了╮(╯▽╰)╭
+-----------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
  • 使用Alembic的好处就是可以控制数据库的版本,增加字段不影响数据。

  • 先试试版本更新的。尝试增加wtfwtfwtfwtfwtf字段,带unique关键字……

    • 提交:alembic revision --autogenerate -m "add wtfwtfwtfwtf."
    • 更新数据库到最新版本:alembic upgrade head
    • 降级到原版本:alembic downgrade -1
class Test(db.Model):...wtfwtfwtfwtfwtf = db.Column(db.String(120), unique=True, nullable=False)...
  • 直接报错了……TypeError: object of type 'NoneType' has no len()
  • 试试添加一个不带unique关键字的字段wawawawawawawawa
class Test(db.Model):...wawawawawawawawa = db.Column(db.String(120), nullable=False)...
  • 更新:alembic revision --autogenerate -m "add wawawawawawa."
  • 升级:alembic upgrade head
mysql> select test.id, test.wawawawawawawawa from test;
+----+------------------+
| id | wawawawawawawawa |
+----+------------------+
|  1 |                  |
|  2 | wawawawawawawawa |
+----+------------------+
2 rows in set (0.00 sec)
  • 降级一波:alembic downgrade -1
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade ad3ed6b3652c -> de464ecfaa3c, add wawawawawawa.# 数据库
mysql> select test.id, test.wawawawawawawawa from test;
ERROR 1054 (42S22): Unknown column 'test.wawawawawawawawa' in 'field list'
  • 重新升级回去:alembic upgrade head
mysql> select test.id, test.wawawawawawawawa from test;
+----+------------------+
| id | wawawawawawawawa |
+----+------------------+
|  1 |                  |
|  2 |                  |
+----+------------------+
2 rows in set (0.00 sec)
  • Alembic升级不会影响数据,但是降级一定丢数据。
  • 文档中写了一句话:We review and modify these by hand as needed, then proceed normally.安全起见,每次提交之后最好review下自动生成的脚本(./alembic/versions/....py)。

其他操作

  • 更新数据库:alembic upgrade 版本号
  • 更新到最新版:alembic upgrade head
  • 升两级:alembic upgrade +2
  • 降级到最初版:alembic downgrade base
  • 降级数据库:alembic downgrade 版本号
  • 降两级:alembic downgrade -2
  • 查看当前版本:alembic current
  • 查看历史版本:
    • alembic history --verbose
    • alembic history -r版本号:版本号
      • alembic history -r-1:current:上一个版本到当前版本
      • alembic history -r8b6caa:6f406f:8b6caa版本(包含)到6f406f版本(包含)
      • alembic history -r8b6caa::8b6caa版本(包含)到当前版本
  • 离线更新(生成SQL):alembic upgrade 版本号 --sql > migration.sql
  • 从特定起始版本生成SQL:alembic upgrade 版本一:版本二 --sql > migration.sql
  • 查询当前数据库版本号:
    • 进入配置的数据库可以看到一个表alembic_version,其中的version_num关键字就是版本号。
  • 清除所有版本:
    • 需要将versions下的所有文件删除,并删除数据库表alembic_version

查看支持的模板

  • alembic list_templates
Available templates:multidb - Rudimentary multi-database configuration.
generic - Generic single-database configuration.
pylons - Configuration that reads from a Pylons project environment.Templates are used via the 'init' command, e.g.:alembic init --template generic ./scripts

附录

  • Auto Generating Migrations
  • OpenStack数据库版本控制工具简介
  • 使用alembic进行数据库版本管理
  • 使用alembic进行数据库版本管理
  • Python数据库ORM工具sqlalchemy的学习笔记

级联删除

来源于:https://www.v2ex.com/t/507065

from flask import Flask
from flask_sqlalchemy import Model, SQLAlchemy
from sqlalchemy import Column, Integer, DateTime, func, String, ForeignKey
from sqlalchemy.orm import relationship, backrefclass BaseModel(Model):id = Column(Integer, primary_key=True)created_at = Column(DateTime(True), default=func.now(), nullable=False)updated_at = Column(DateTime(True), default=func.now(), onupdate=func.now(), nullable=False)@classmethoddef create(cls, **kw):session = db.sessionif 'id' in kw:obj = session.query(cls).get(kw['id'])if obj:return objobj = cls(**kw)session.add(obj)session.commit()return objdef to_dict(self):columns = self.__table__.columns.keys()return {key: getattr(self, key) for key in columns}app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://root:iqiyi123@localhost:3306/test'db = SQLAlchemy(app, model_class=BaseModel)######################################################################################################class Parent(db.Model):__tablename__ = 'parent'id = Column(Integer, primary_key=True)name = Column(String(20))class Child(db.Model):__tablename__ = 'child'id = Column(Integer, primary_key=True)name = Column(String(20))# 无法删除parent_id = Column(Integer, ForeignKey('parent.id'))parent = relationship("Parent")# 父级,子级一起删除# parent_id = Column(Integer, ForeignKey('parent.id', ondelete="CASCADE"))# parent = relationship("Parent", backref=backref("child", passive_deletes=True))# 父级删除,子级不删除,外键更新为null# parent_id = Column(Integer, ForeignKey('parent.id', ondelete="CASCADE"))# parent = relationship("Parent", backref=backref("child"))db.create_all()
Parent.create(name='ZhangTian')
Parent.create(name='LiTian')
Child.create(name='ZhangDi', parent_id=1)
Child.create(name='LiDi', parent_id=2)parent = db.session.query(Parent).first()
db.session.delete(parent)
db.session.commit()

SqlAlchemy Alembic数据库升级与降级简易教程相关推荐

  1. Android数据库升级、降级、创建(onCreate() onUpgrade() onDowngrade())的注意点

    以下内容可以作为面试官在面试的时候的问题,感觉比较好,是比较常用的知识点,可以用来考察基础是否扎实. 也可以程序猿学习.开发中的注意点.因为稍微不注意,就有可能导致数据库不能用. DBAdapter. ...

  2. 用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级

    Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 Alembic 查看指令 manager db 的可用选项 ...

  3. android数据库降级_Android SQLite (二.数据库创建,升级及降级)

    上篇文章简介和常用语法介绍了SQLite数据库的基本信息和一些常用的语法操作,本篇文章主要介绍Android开发过程中SQLite数据库的创建使用和常见问题处理. 一.SQLiteOpenHelper ...

  4. oracle降版本导出,Oracle 数据库 升级/降级 兼容性矩阵

    引用自: Doc ID 1577660.1 升级到19c的升级兼容性矩阵 能够直接升级到Oracle Database 19c的数据库最小版本https://www.cndba.cn/cndba/da ...

  5. 域控服务器降级失败,windows2003域控制器升级和降级的图文教程

    windows2003域控制器升级和降级 按照微软的说法,一般网络中过的PC数目低于10台,则建议建议采对等网的工作模式,而如果超过10台,则建议采用域的管理模式,因为域可以提供一种集中式的管理,这相 ...

  6. 【与达梦同行】数据库升级实战教程

    1 背景 众所周知,IT行业发展日新月异.IT技术革新也是以迅猛著称. 达梦作为最优秀数据库厂商之一在数据库的研发上也是不遗余力,根据市场,客户,前沿技术稳定的对达梦数据库进行迭代. 达梦的数据库新版 ...

  7. Alembic数据库迁移工具使用

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

  8. android数据库降级_android——数据库版本升/降级问题

    数据库版本升级 在开发android应用程序的时候,一般由于在我们开发的时候我们不知道以后会后什么新功能,也有可能增加业务逻辑(也就是更新),可想而知我们原来的数据库结构可能不适用已更新的应用,那么应 ...

  9. Ocelot简易教程(一)之Ocelot是什么

    Ocelot简易教程(一)之Ocelot是什么 原文:Ocelot简易教程(一)之Ocelot是什么 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/955 ...

最新文章

  1. 海康设备字母编号含义 H5:0,H7:1,KT2:2,G5:3
  2. 数据分析:JSON格式数据
  3. Django后台管理
  4. CentOS 7配置IP的几种方法。
  5. Amazon Aurora 深度探索
  6. ceph常用命令-pool相关命令
  7. [转载]CentOS6nbsp;快速搭建轻量级远程桌面nbsp;Xfcenb
  8. C++类型转换基本语法
  9. React Native集成Redux框架讲解与应用
  10. 计算图层面积(针对于面要素)ArcObject c++
  11. java面试宝典2019_Java面试宝典2019完整版.doc
  12. RabbitMQ生产者和消费者Java实现
  13. Apache Mnemonic成为Apache顶级项目,主要解决大数据性能问题
  14. angularjs 实例_AngularJS过滤器示例教程
  15. JAVA 反射 动态获取类,并调用方法
  16. 腾讯云服务器安装AMH控制面板
  17. Android Studio 写个单元测试用例,就是这么方便
  18. 解决问题就像剥洋葱,解决的关键是基础知识
  19. 计算机软件著作权评估
  20. win10电脑连接蓝牙请检查PIN并重新连接

热门文章

  1. 很认真的聊一聊程序员的自我修养(转自博客园)
  2. Android项目练手
  3. ARKit-带你走进全新的世界(三:追踪/距离感应/AR尺子)
  4. 关于离职证明和竞业条款
  5. Translate Shell使用
  6. 【上海科技大学】考研初试复试资料分享
  7. MyEclipse下SVN的配置
  8. C++左值与右值の深思——万能引用与完美转发
  9. 创建电子文件封装(EEP)包的功能代码
  10. HTML大学生动漫网页设计作业源码 ~ 火影忍者动漫7页面带特效带轮播(HTML+CSS+JavaScript)...