一次 rename 欄位跟程式一起上線,把正式站打成一片 500——不停機改 schema 的完整流程
DevOps·2026年6月17日·5 分鐘閱讀

一次 rename 欄位跟程式一起上線,把正式站打成一片 500——不停機改 schema 的完整流程

L
Leo Wu

後端工程師,做過金流與高併發系統,被一次欄位改名打穿正式站之後,再也不敢小看線上 schema 變更。

一次「應該很安全」的改名

一次 rename 欄位跟程式一起上線,把正式站打成一片 500——不停機改 schema 的完整流程:本文架構

那次我很有信心,結果翻車翻得很難看。

需求只是把一個欄位改個更清楚的名字。我在一個 migration 裡把欄位 rename 了,程式碼也同步改成用新名字,兩個一起打包上線。在我的認知裡這很乾淨——舊名字沒人用了,改掉剛好。

問題是,正式環境的部署不是一瞬間完成的。新版程式在滾動更新,一部分機器已經是新版(用新欄位名),一部分還是舊版(用舊欄位名)。而資料庫的 rename 是瞬間生效的。於是那個滾動更新的空窗期裡,還沒更新的舊版程式去查那個「已經被改名、不存在了」的舊欄位,直接噴 500。正式站有好幾分鐘是一片紅的。

這篇就是那次之後,我整理出來、後來再也沒讓我出過事的「不停機改 schema」流程。核心只有四個字:向後相容。

為什麼線上改 schema 這麼危險

關鍵就在上面那個場景:資料庫的變更是即時的,但程式的部署是漸進的。 這中間一定有一段時間,新舊版本的程式碼同時在線上,共用同一個資料庫。

所以任何一次 schema 變更,你都要問自己一個問題:在新舊程式並存的那個空窗期,這個 schema 對兩個版本的程式都還能正常運作嗎? 我那次就是完全沒想到「舊版程式還活著」這件事。

核心原則:向後相容

所有安全的 schema 變更,都建立在「每一步都向後相容」上。意思是:這一步改完之後,還沒更新的舊程式,也不能壞。做不到向後相容的變更,就要拆成好幾個都向後相容的小步驟,分次上線。

加欄位:看似簡單也有坑

加欄位是最安全的操作,因為舊程式根本不知道新欄位存在,不受影響。但也有坑:新欄位如果設成 NOT NULL 又沒有預設值,加的當下就會因為既有資料沒值而失敗。所以加欄位先加成可為空、或帶預設值,之後再慢慢收緊。

改欄位、改名:擴張收縮模式

這是我那次血的教訓換來的正確姿勢。要改一個欄位(改型別、改名字),絕對不要一步到位,要用擴張收縮(expand–contract)

  1. 1.擴張:先新增新欄位/新格式,舊的保留。這步向後相容,舊程式用舊的、不受影響。
  2. 2.遷移:程式改成「同時寫新舊兩邊」,並把存量資料回填到新欄位。這段時間新舊並存,兩邊都有資料。
  3. 3.切換:等所有程式都改成讀新欄位、且完全上線之後,才把讀取切到新的。
  4. 4.收縮:確認再也沒有任何程式碼碰舊欄位了,才把舊欄位刪掉。

我那次的 rename 如果拆成這四步,中間每一步舊程式都活得好好的,就不會有那片 500。改名不是一個動作,是四個分開上線的動作。

刪欄位跟改名:絕對不能跟程式部署綁在一起

這是我那次最直接的教訓,單獨拉出來講:任何「移除」或「改名」類的變更,絕對不能跟用到它的程式部署綁在同一次上線。 一定要先確定沒有任何一個版本的線上程式還在用它,才能動手刪。刪除永遠是最後一步,而且是獨立的一步。

大表加索引:別用會鎖表的方式

在大表上直接加索引,很多資料庫預設會鎖住整張表,鎖的期間讀寫全部卡住——這跟停機沒兩樣。要用不鎖表的方式建索引(PostgreSQL 的 CREATE INDEX CONCURRENTLY 就是為此而生)。上線前先確認你的加索引語句到底會不會鎖表,別在尖峰時段才發現整張表被你鎖死。

回填大量資料:一定要分批

要把存量資料回填到新欄位時,千萬別用一句 UPDATE 掃全表。幾百萬列的單一 UPDATE 會產生巨大的交易、長時間鎖住大量資料列、把資料庫的資源吃光。要分批,一次更新一小塊,中間讓資料庫喘口氣。慢一點沒關係,穩比快重要。

遷移要可回滾

每一個 migration,我都會先想好「如果這步上線後發現不對,怎麼退回去」。有些操作(尤其是刪除)是不可逆的,這種我會格外謹慎,甚至先備份。可回滾這件事,你會在某次半夜發現遷移出錯、需要緊急退版的時候,無比感激當初的自己。

跟程式部署的順序:實戰編排

把上面串起來,一次安全的欄位變更,實際的上線順序大概是:先上相容的 schema 變更(擴張)→ 再上「同時讀寫新舊」的程式 → 回填存量資料 → 上「只讀新的」程式 → 全部穩定後,最後獨立上一次「刪掉舊欄位」。

看起來很囉唆,要分好幾次上線。但跟正式站一片 500、跟我那幾分鐘的手忙腳亂比起來,這點囉唆太划算了。

一些救過我的小習慣

  • 上線前在一個資料量接近正式環境的地方,把 migration 完整跑一遍,看它到底跑多久、會不會鎖表。
  • 大的變更挑離峰時段做,給自己留犯錯的餘裕。
  • 把「這次改動在新舊程式並存時會怎樣」當成每次 migration 的必答題——這一題我那次沒答,就付了代價。

小結

那片 500 教我的事很簡單:在正式環境,資料庫和程式碼永遠不會在同一瞬間更新,你的每一步都得讓新舊版本同時活得下去。 向後相容、擴張收縮、刪除獨立成最後一步——這些流程不是理論潔癖,是我對著一整片紅色的監控畫面、手忙腳亂那幾分鐘換來的。改 schema 不難,難的是記得那個你看不見、卻真實存在的新舊並存空窗期。

#資料庫遷移#零停機#PostgreSQL#MySQL#高併發#DevOps

留言討論

有想法、有不同經驗、或想糾正我?歡迎在下面留言,免註冊,填個暱稱就能留。

相關文章