使用 Alembic 來進行資料庫版本管理

Mike
11 min readOct 15, 2018

--

前一陣子在看「Flask 網頁開發」這本書的時,看到書中有介紹「Flask-migrate」來管理不同版本間的資料庫結構轉換,其底層就是使用 Alembic 來達到這個功能。

因為最近在動手寫看看 python 程式,剛好需要使用到這類的功能,但並沒有使用到 Flask,所以就稍微研究了一下單獨使用 Alembic 這個套件來管理資料庫的結構變更。

首先安裝 Alembic,在終端機(Console)中輸入以下的pip指令安裝。由於Alembic 這個套件是搭配 SQLAlchemy 使用,所以若你的 Python 環境中沒有安裝 SQLAlchemy 這個套件的話,請先一併安裝。

$pip install alembic

安裝完後,在輸入 alembic init myAlembic 來初始化環境。 init後面的參數為自訂的目錄名稱,執行後 alembic 會建立相關的目錄及檔案。

首先,開啟 alembic.ini 這個檔案,並在 sqlalchemy.url 這個屬性後面,輸入所要管理的資料庫 URL,URL 的寫法請參考 SQLAlchemy 的 Engine Configuration (http://docs.sqlalchemy.org/en/latest/core/engines.html),在這裡我的資料庫是使用 SQLite 來做示範。

修改完後,就可以來手動建立第一個 migration script。在 console 中輸入 alembic revision -m "create user table" ,執行後 alembic 就會在自訂的目錄底下的 versions 目錄中產生一個py檔;預設的檔案名稱命名方式為隨機的「版本編號」加上剛才在 -m 後面所輸入的文字。

手動建立 migration script 時,alembic 只會在生成的檔案中,先行 import alembic.op 及 sqlalchemy ,並且將對應的 revision ID 自動帶入 revision 及 down_revision 中,這兩個變數分別代表目前這個 migration script 的版本及前一個版本,因為目前只先產生了第一個版本,所以你會看到這個檔案中的down_revision 數值為 None。接下來,我們只需要在 upgrade 及 downgrade 這兩個 function 中,撰寫相關的資料庫結構變更的程式碼。

首先,在 upgrade() 中使用 op.create_table 來建立一個叫 user 的 table,並使用 sa.Column 來定義 userID 及 userName 這兩個欄位,將這兩個欄位的type 分別設為 Integer 及 String,及使用 primary_key=true 來將 userID 設為該 table 的 key,而 nullable=False 則表示這個欄位的值不可為 null。而在downgrade()中則是進行相反的操作,用 op.drop_table 來刪除 user 這個table。

撰寫好 upgrade 及 downgrade 中所要進行的操作後,就可在 console 中輸入 alembic upgrade head,將資料庫結構更新至最新的版本。

將資料庫的版本更新後,就可看到 Alembic 會自動執行我們剛才在 upgrade() 中所定義的操作,在資料庫中新增剛才我們所定義的 table 及其中的欄位。此外,第一次使用時,Alembic 也會在該 Database 中新增一個 alembic_version 的 table,用來紀錄目前資料庫結構的版本編號。

接著,我們用同樣的方式手動建立第二個 migration script,並在 upgrade()中使用 op.add_column,在 user 這個 table 中新增一個 birthDay 欄位,並將type 設定為 sa.Date。相對的,在 downgrade() 中,我們使用op.drop_column(‘user’, ‘birthDay’) 來將 user table 中的 birthDay 欄位移除。

將資料庫 upgrade 後,你可以看到 user 的資料表中多了一個 birthDay 欄位。同時,你也會看到在 alembic_version 資料表中的 version_num 只有一筆資料,其內容為目前的資料庫版本編號 67c0168b246c。

現在來介紹alembic current及alembic history這兩個指令。當你在console中輸入 alembic current 會顯示目前資料庫的版本編號,而輸入 alembic history 會顯示資料庫版本的歷史紀錄。

在 console 中輸入 alembic downgrade -1 就可以將目前的資料庫版本降至前一個版本。downgrade後面的參數代表你所要回復至哪個版本,若你填入-2,代表版本會回復至前兩個版本;你也可以直接輸入所要回復的版本編號。若你跟我一樣是使用 SQLite,應該會看到下面這個錯誤訊息,因為 SQLite 本身並不支援 ALTER 這個 SQL 語法,後面會提到如何解決 SQLite 上的這個問題。

接下來說明如何讓 Alembic 自動產生資料庫版本的遷移腳本(migration script)。若你有使用 SQLALchemy 中的 ORM,就可使用 Alembic 來自動產生遷移腳本。首先,依照目前的資料庫結構,定義一個 User model。

接著,在 User model 中加入一個 nickName 的欄位,並將 type 設為UnicodeText。

在使用 autogenerate 這個功能前,需先修改 alembic 專案目錄底下的 env.py 這個檔案,在最前面 import User,並且將 target_metadata = None 修改為 target_metadata = User.metadata;如果你有多個model的話,可以用官方文件中的方式(http://alembic.zzzcomputing.com/en/latest/autogenerate.html#autogenerating-multiple-metadata-collections),用 list 把 metadata 包起來再設定給target_metadata。

接著在 console 中輸入 alembic revision --autogenerate -m "Change column data type" ,在 revision 後面加上 autogenerate 這個參數來讓 alembic 自動在所建立的 migration script 中的 upgrade 及 downgrade,產生對應的程式碼。

開啟 migration script 後,就可看到 Alembic 會自動幫你產生對應的程式碼。這個功能雖然好用,但不能保證自動產生出來的程式碼會跟你所想像的一樣,所以還是要檢查一下所產生出來的程式碼是否符合你的預期。

執行 upgrade 後可以看到在 user table 中多了一個 nickName 的欄位。

最後,要說明一下資料型態比對及之前提到的 SQLite 的 ALTER 問題。

如果你用 autogenerate 的方式產生 migration script,在預設的情況下是不會針對欄位的資料型態做比對,例如:birthDay 原本是 Date,若在 model 中將型態改為 String,用 autogenerate 是不會主動去偵測資料型態是否有變更並產生對應的程式碼。若使用 autogenerate 時要比對資料型態,要在 env.py 中的 run_migrations_online(),裡面的 context.cinfigure 中,加入 compare_type=True ,這樣在 autogenerate 時就會比對資料型態,如下面這張圖中的設定,你也可以看到有另一個 render_as_batch 的參數是設為 True,這個設定就是用來解決 SQLite 無法執行 ALTER 指令的問題。

修改完存檔後,我們首先將 User model中的 nickName 欄位移除,並加入 address 這個欄位;另外將 birthDay 這個欄位的 data type 修改為 String。

接著使用 autogenerate 後,可以看到自動產生的 migration script 中,除了刪除 nickName 這個欄位及增加 address 欄位外,還多了將 birthDay 欄位的資料型態修改為 String;跟之前 autogenerate 不同的地方是,在 upgrade 及 downgrade 中是使用 op.batch_alter_table 這個 function,詳細說明可以參考官方文件中的batch_alter_table說明。其運作方式就是先依照修改(upgrade或downgrade)後的 table 結構建立一個新 table 並使用一個臨時的名稱;將原有的 table 中資料移至新的 table 中後,再把舊有的 table 移除並將新建立的 table 名稱改為原有的 table 名稱,這樣就可以解決 SQLite 無法執行 ALTER 指令問題。

執行 upgrade 後,就可看到 user table 中的欄位已跟我們所定義的 user model 一樣。而使用 batch_op 後,在 SQLite 上也可以正常使用 downgrade 功能了。

在開發初期,因為資料庫的結構還沒定案且會經常變動的情況下,使用 Alembic 這類的 migration tool,可以減少不同版本程式碼所對應的資料庫結果不同所帶來的困擾,讓程式開發人員可以專心在軟體的開發上。

參考資料:
SQLAlchemy - Engine Configuration http://docs.sqlalchemy.org/en/latest/core/engines.html

Alembic - Working with Branches
http://alembic.zzzcomputing.com/en/latest/branches.html

Alembic — Auto Generating Migrations
http://alembic.zzzcomputing.com/en/latest/autogenerate.html

Autogenerating Multiple MetaData collections
https://alembic.zzzcomputing.com/en/latest/autogenerate.html#autogenerating-multiple-metadata-collections

Alembic — batch_alter_table
https://alembic.zzzcomputing.com/en/latest/ops.html#alembic.operations.Operations.batch_alter_table

OpenStack数据库版本管理工具介绍 ttps://zhuanlan.zhihu.com/p/37024338

--

--

Mike

3 year+ coding experience in Angentflow BPM, JavaScript. Now I’m learning Python & Django.