前言:什麼是 DB Migration?為什麼我們需要?
當我們大量使用關聯式資料庫時,一定會用到許多 table 來做數據的儲存,而在每一張表下又有不一樣的欄位定義;有時候想要增加欄位、有時想要刪減、有時要轉換格式、有時要下註解….,等於說 Schema 會隨著開發需求變來變去,我們有沒有一種更有效率的方式,來紀錄這些變化呢?
有的~答案就是標題的「DB Migration」。可以簡單把它想像成是:資料庫的版本控制,所有的schema變換(Migration)都會被清楚地紀錄下來,以及對應的版本號。開發過程可以隨著需求不斷地上版,若中間有任何錯誤或是bug,也可以隨時退版到穩定的版本號。
我們使用 Python 一套工具:alembic,就可以體會 Migration 的威力了👍
1. 首先,讓我們安裝套件
$ pip install alembic
安裝完後,可以指令 $ alembic --version
來查看是否安裝完成
2. 初始化
輸入指令 $ alembic init alembic
會在當前目錄創建一個資料夾 /alembic
,裡頭會有各種設定檔,也會有一個 alembic.ini
初始化檔
3. 讓 alembic 知道該讀取哪個資料庫
首先,在 /alembic/env.py
中加入設定區域。在這邊的範例我以 mysql 當範例,也可以設定 postgressql 或其他常見的資料庫,詳細可看官方文檔。
################### Setting Block ######################
import os
from dotenv import load_dotenv
load_dotenv()
mysql_user = os.environ.get("ALEMBIC_USER")
mysql_password = os.environ.get("ALEMBIC_PASSWORD")
mysql_server = os.environ.get("ALEMBIC_SERVER")
mysql_db = os.environ.get("ALEMBIC_DB")
mysql_dsn = f'mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_server}/{mysql_db}'
# postgres_dsn = f'postgresql+psycopg2://{postgres_user}:{postgres_password}@{postgres_server}/{postgres_db}'
config = context.config
config.set_main_option('sqlalchemy.url', mysql_dsn)
################### Setting Block ######################
接著,在當前目錄新增一個 .env
,並加入以下內容,讓 alembic 知道要做 migration 的資料庫是什麼:
# alembic
ALEMBIC_SERVER='test-mig.us-east-2.rds.amazonaws.com' #輸入database server
ALEMBIC_USER='admin'
ALEMBIC_PASSWORD=''
ALEMBIC_DB='test_mig'
修改 alembic.ini
裡的 file_template,這樣出來的 version 就會有時間跟版號的資訊:
file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s_%%(slug)s
# 格式會像是:yyyymmdd_版本號_生成名稱
# 20220711_a48d7e2b701d_create__song_table.py
4. 開始撰寫 migration 的程式碼
💡 情境:我想要創建一個 user 表,用來記錄所有用戶的id與姓名
輸入指令 alembic revision -m "create user table"
,接著就會生成一個 .py 檔案再 /versions
資料夾下:
再來我們就來修改新生成的 日期_版號_create_user_table.py
,加入以下內容:
# 新增一張表
def upgrade():
op.create_table(
'user',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('customer_id', sa.Unicode(10), comment='客戶編號'),
sa.Column('name', sa.Unicode(10), comment='客戶姓名'),
comment='測試客戶表',
)
# 記得要撰寫相對應的降版
def downgrade():
op.drop_table('user')
5. 來正式 migrate 看看
對 db-migration 進行上版 upgrade,輸入指令 alembic upgrade +1
接著可以去 UI 查看,真的有建立了表,其中還自動生成了一個 alembic_version 的表,裡頭會紀錄剛剛過版的版號:
當然,我們也可以進行退版,只要輸入 alembic downgrade -1
,就會自動進進行退版,剛剛的 user 表就會被 drop 掉:
重新整理 UI,就可以查看到 user 真的沒了,alembic_version 表面紀錄的版號也會自動消失:
操作到這邊,已經可以大致上了解整個 migrate 的核心精神,讀者可以自行操作其他情境,例如:
💡 新增一個「age」欄位,註記客戶的年齡
我們可以如法炮製,先使用指令 alembic revision -m "add age"
新增一個 .py 檔:
接著去修改這份 .py,撰寫 migrate:
def upgrade():
op.add_column('user', sa.Column('age', sa.Integer, comment='客戶年齡'))
def downgrade():
op.drop_column('user', 'age')
馬上再來升一版看看 $ alembic upgrade +1
:
資料表中真的多了一個「age」欄位😎
版本紀錄
所有的一切,都儲存在 /alembic/versions
這個資料夾下,大部分的專案也會把這些列入 git 版控,因此資料庫做了什麼變更,基本上都能夠版本控制。如果有人不小心把 schema 搞壞,還可以透過指令隨時退到自己想要的版本,好東西不用嗎?🤩
延伸閱讀
到了這邊,希望讀者能多少了解到 db_migration 的用意以及好處,其實官方文件對於指令的範例,描述得很清楚
可以嘗試許多不同情境,例如刪欄位、修改格式等等
上述的程式碼,都有放在 github 上了,有興趣的讀者可以抓下來玩玩,實際操作過一遍,才能體會 migrate 的遷徙感🥴
P.S記得修改環境變數檔 .env
改成自己的資料庫的連線