使用Flask-Migrate遷移數據庫
在開發時,以刪除表再重建的方式更新數據庫簡單直接,但明顯的缺陷是會丟掉數據庫中的所有數據。在生產環境下,沒有人想把數據都刪除掉,這時需要使用數據庫遷移工具來完成這個工作。SQLAlchemy的開發者Michael Bayer寫了一個數據庫遷移工作—Alembic來幫助我們實現數據庫的遷移,數據庫遷移工具可以在不破壞數據的情況下更新數據庫表的結構。蒸餾器(Alembic)是煉金術士最重要的工具,要學習SQL煉金術(SQLAlchemy),當然要掌握蒸餾器的使用。
擴展Flask-Migrate繼承了Alembic,提供了一些flask命令來簡化遷移工作,我們將使用它來遷移數據庫。Flask-Migrate及其依賴(主要是Alembic)可以使用pipenv安裝:
(Lenovo-ezd1lI9Y) C:\Users\Lenovo>pipenv install flask-migrate
在程序中,我們實例化Flask-Migrate提供的Migrate類,進行初始化操作:
from flask import Flask, render_template, flash, url_for, redirect from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate app = Flask(__name__) db = SQLAlchemy(app) migrate = Migrate(app, db) # 在db對象創建后調用
實例化Migrate類時,除了傳入程序app,還需要傳入實例化Flask-SQLAlchemy提供的SQLAlchemy類創建的db對象作為第二個參數。
1、創建遷移環境--flask db init
在開始遷移數據之前,需要先使用下面的命令創建一個遷移環境:
(Lenovo-ezd1lI9Y) D:\flask\FLASK_PRACTICE\DataBase>flask db init Creating directory D:\flask\FLASK_PRACTICE\DataBase\migrations ... done Creating directory D:\flask\FLASK_PRACTICE\DataBase\migrations\versions ... done Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\alembic.ini ... done Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\env.py ... done Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\env.pyc ... done Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\README ... done Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\script.py.mako ... done Please edit configuration/connection/logging settings in 'D:\\flask\\FLASK_PRACTICE\\DataBase\\migrations\\alembic.ini' before proceeding.
Flask-Migrate提供了一個命令集,使用db作為命令集名稱,它提供的命令都以flask db開頭。你可以在命令行中輸入flask—help查看所有可用的命令和說明。
(Lenovo-ezd1lI9Y) D:\flask\FLASK_PRACTICE\DataBase>flask db --help Usage: flask db [OPTIONS] COMMAND [ARGS]... Perform database migrations. Options: --help Show this message and exit. Commands: branches Show current branch points current Display the current revision for each database. downgrade Revert to a previous version edit Edit a revision file heads Show current available heads in the script directory history List changeset scripts in chronological order. init Creates a new migration repository. merge Merge two revisions together, creating a new revision file migrate Autogenerate a new revision file (Alias for 'revision... revision Create a new revision file. show Show the revision denoted by the given symbol. stamp 'stamp' the revision table with the given revision; don't run... upgrade Upgrade to a later version
遷移環境只需要創建一次。這會在你的項目根目錄下創建一個migrations文件夾其中包含了自動生成的配置文件和遷移版本文件夾。
2、生成遷移腳本--flask db migrate -m "add note timestamp"
使用migrate子命令可以自動生成遷移腳本:
(Lenovo-ezd1lI9Y) D:\flask\FLASK_PRACTICE\DataBase>flask db migrate -m "add note timestamp" INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.autogenerate.compare] Detected removed table u'draft' INFO [alembic.autogenerate.compare] Detected removed table u'post' INFO [alembic.autogenerate.compare] Detected removed table u'comment' Generating D:\flask\FLASK_PRACTICE\DataBase\migrations\versions\cdd9d12762fc_add_note_timestamp.py ... done
這條命令可以簡單理解為在flask里對數據庫(db)進行遷移(migrate)。-m選項用來添加遷移備注信息。從上面的輸出信息我們可以看到,Alembic檢測出了模型變化:表note新加了一個timestamp列,並且相應生成了一個遷移腳本cdd9d12762fc_add_note_timestamp.py:
"""add note timestamp Revision ID: 7f3dae8cae4d Revises: Create Date: 2019-04-01 21:56:32.469000 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '7f3dae8cae4d' down_revision = None branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('draft') op.drop_table('post') op.drop_table('comment') op.add_column('note', sa.Column('timeStamp', sa.String(length=70), nullable=True)) op.create_unique_constraint(None, 'note', ['timeStamp']) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_constraint(None, 'note', type_='unique') op.drop_column('note', 'timeStamp') op.create_table('comment', sa.Column('id', sa.INTEGER(), nullable=False), sa.Column('body', sa.TEXT(), nullable=True), sa.Column('post_id', sa.INTEGER(), nullable=True), sa.ForeignKeyConstraint(['post_id'], [u'post.id'], ), sa.PrimaryKeyConstraint('id') ) op.create_table('post', sa.Column('id', sa.INTEGER(), nullable=False), sa.Column('title', sa.VARCHAR(length=50), nullable=True), sa.Column('body', sa.TEXT(), nullable=True), sa.PrimaryKeyConstraint('id') ) op.create_table('draft', sa.Column('id', sa.INTEGER(), nullable=False), sa.Column('body', sa.TEXT(), nullable=True), sa.Column('edit_time', sa.INTEGER(), nullable=True), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ###
從上面的代碼可以看出,遷移腳本主要包含了兩個函數:upgrate()函數用來將改動應用到數據庫,函數中包含了向表中添加timestamp字段的命令,而downgrade()函數用來撤消改動,包含了刪除timestamp字段的命令。
就像這兩個函數中的注釋所說的,遷移命令是有Alembic自動生成的,其中可能包含錯誤,所以有必要在生成后檢查一下。
因為每一次遷移都會生成新的遷移腳本,而且Alemic為每一次遷移都生成了修訂版本(revision)ID,所以數據庫可以恢復到修改歷史中的任一點。正因如此,遷移環境中的文件也要納入版本控制。
有些復雜的錯誤無法實現自動遷移,這時可以使用revision命令手動創建遷移腳本。這同樣會生成一個遷移腳本,不過腳本中的upgrade()和downgrade()函數都是空的。你需要使用Alembic提供的Operations對象指令在這兩個函數中實現具體操作,具體可以訪問Alembic官方文檔查看。
3、更新數據庫--flask db upgrade
生成了遷移腳本后,使用upgrade子命令即可更新數據庫:
(Lenovo-ezd1lI9Y) D:\flask\FLASK_PRACTICE\DataBase>flask db upgrade INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.runtime.migration] Running upgrade -> 7f3dae8cae4d, add note timestamp ERROR [root] Error: No support for ALTER of constraints in SQLite dialect
如果還沒有創建數據庫和表,這個命令會自動創建,如果已經創建,則會在不損壞數據的前提下執行更新。
從客戶端可以看到note表新增加了一個timestamp字段
如果你想回滾遷移,那么可以使用downgrade命令(降級),它會撤銷最后一次遷移在數據庫中的改動,這在開發時非常有用。比如,當執行upgrade命令后發現某些地方出錯了,這時就可以執行flask db downgrade命令進行回滾,刪除對應的遷移腳本,重新生成遷移腳本后再進行更新(upgrade)。
SQLite遷移問題:flask db downgrade 報錯
(Lenovo-ezd1lI9Y) D:\flask\FLASK_PRACTICE\DataBase>flask db downgrade 5e87b4da6187 INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. ERROR [root] Error: Destination 5e87b4da6187 is not a valid downgrade target from current head(s)
是因為sqlite不支持alter機制的原因
開發時是否需要遷移?
在生產環境下,當對數據庫結構進行修改后,進行數據庫遷移是必要的。因為你不想損壞任何數據,在生成自動遷移腳本之后,執行更新之前,對遷移腳本進行檢查,甚至是使用備份的數據庫進行遷移測試,都是有必要的。
在開發環境中,你可以按需要選擇是否進行數據庫遷移,對於大多數程序來說,可以在開發時使用虛擬數據生成工具來生成虛擬數據,從而避免手動創建記錄進行測試,這樣每次更改表結構時,可以直接清楚后重新生成,然后生成測試數據,這要比執行一次遷移簡單的多(后續會學到通過一個命令完成所有工作),除非生成虛擬數據耗費的時間過長。
另外,在本地本地開發時通常使用SQLite作為數據庫引擎。SQLite不支持ALTER語句,而這正是遷移工具依賴的工作機制,也就是說,當SQLite數據庫的字段刪除或修改后,我們沒法直接使用遷移工具進行更新,你需要手動添加遷移代碼進行遷移。在開發中,修改和刪除列是很常見的行為,手動操作遷移會花費太多時間。
對於SQLite,遷移工具一般使用”move and copy”的工作流(創建新表、遷移數據、刪除舊表)達到類似的效果。
如果希望讓生產環境的部署更搞笑,則應該盡可能讓開發環境和生產環境保持一致。這時應該考慮直接在本地使用MySQL或其他的DBMS,然后設置遷移環境。