[Python] 實作 db-migration

PC Chen
程式乾貨
Published in
8 min readDec 30, 2022
db migration

前言:什麼是 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 改成自己的資料庫的連線

--

--

PC Chen
程式乾貨

喜歡接觸與動手實作各種軟體技術的後端數據工程師 A data- backend engineer who is enthusiastic in learning and implementing any techniques in software engineering.