在產品迭代初期或者系統重構時期,業務模型的調整帶來資料結構的變化,資料移轉不可避免。做好資料移轉需要考慮周全,且準備充分,做好預案,否則如果出現資料不一致問題,錯誤修正成本高,同時核心業務資料的錯誤,會引起客戶/業務方的投訴,團隊也會承受巨大的壓力。
本文結合最近一個實際項目的資料資料移轉過程,講述了踩過的坑,加上自己的一些思考得出的一些方法論,最後給出了資料移轉個指令碼的一個執行個體
目標
確保新用戶端訪問新業務模型時能否正常查詢之前的資料,如訂單等;不會出現資料不一致。
原則
- 影響可控 —— 只對需要遷移對資料做修改,不能影響到其他資料;不到萬不得已,不會允許停機遷移資料,因此遷移視窗期越短越好,減少遷移視窗期使用者行為帶來的資料問題;
- 可回退 —— 一旦發現遷移資料有問題,可以回退到之前的資料狀態;
- 可追溯 —— 出現問題,能夠有日誌或者備份資料可查;資料庫的binlog,遷移程式的log可以作為依據;
- 可測試 —— 遷移方案必須可測試,要滿足可測試,那麼遷移方案必須是通用型的方案。
思路
- 先備份,再遷移;
- 遷移後需要做資料比對,確保資料一致性;
- 出現問題,考慮是否做回退【並不是所有情境都能直接回退】;
- 如果業務量大,為避免使用者行為和資料移轉產生衝突,考慮停服務遷移。
步驟
- 備份 —— 將待遷移資料備份到bak表,任何在遷移過程中會被修改的資料應當被備份,任何在insert情境被當著原資料使用的資料應當被備份;
- 遷移 —— 遷移指令碼 / 程式 只對bak表中的目標資料做操作;
- 驗證 —— 遷移完成後,需要做資料核對,確保資料一致性;
- 回退 —— 回退指令碼同樣只對bak表中的目標資料做操作,且行為和遷移指令碼行為相反。
方案的選擇資料庫指令碼
在資料量小,業務情境簡單的情況下非常適合,簡單輕量級,通過sql指令碼完成資料移轉非常合適。但如下幾點需要認真思考:
- 能否寫成通用sql?如果不能放棄;因為為不同環境準備不同的指令碼,破壞了‘可測試’這一原則;
- 被操作的資料量是否太多?如果過多(通常超過1萬條就很多了),可能導致指令碼提交逾時;
- 業務情境是否複雜?如果涉及到的表過多(超過3張),指令碼的執行存在先後順序,這時候通過人為保證,風險會大大增加;
- 測試環境和線上環境的sql執行工具/環境是否一致?如果不一致(很多公司的DBA工具會對一些文法和格式作出限制,比如不能有換行,注釋中不能有半形分號等),則也會破壞掉‘可測試’這一原則;
- 線上sql執行流程是否冗長?如果流程冗長(公司的流程可能要求需要TL和DBA的審批,DBA作為第三方資源依賴不可控),且指令碼多,會拉長資料移轉的視窗期,業務風險大大增加,破壞了‘影響可控’的原則。
遷移程式
和‘資料庫指令碼’方法相反,撰寫的‘遷移程式’能夠避開這些缺點,更適合於業務情境複雜,資料量大的情境。
方案對比
項 |
指令碼 |
程式 |
備忘 |
通用性 |
不完全 |
完全 |
如:依賴第三方資料時,指令碼無法做到通用 |
複雜情境支援 |
不適合 |
適合 |
複雜業務情境指令碼不適合,如迴圈調用,第三方系統資料,多表依賴等 |
大資料量支援 |
不適合 |
適合 |
大資料量可能導致指令碼提交逾時,通常超過1萬條不宜使用指令碼,大多數dba工具通常也會對操作的資料量做限制 |
開發成本隨複雜度增長 |
指數 |
線性 |
|
無論採用‘資料庫指令碼’還是‘遷移程式’,都需要遵循上面的‘原則’和步驟。
思考
資料移轉和應用程式發布的先後順序?
在業務量大的情況下,資料移轉過程中,資料被使用者行為修改了怎麼辦?
遷移失敗,什麼情況下做回退,什麼情況下不能做回退?
踩過的坑
在轉贈2.0項目中,前期對資料移轉的業務複雜度預估不足,選擇了‘資料庫指令碼’方式,踩了不少坑:
- 在測試環境測試通過的sql指令碼,無法直接線上上環境執行,原因是線上資料庫工具對sql格式和內容校正更為嚴格(不允許有換行等);
- 測試過程中發現,需要實現為一個訂單迴圈產生多個券碼的情境,雖通過sql間接實現,但是複雜度很高,開發成本增大;
- 由於sql較多且複雜度高(如:使用insert select),需要經過TL和DBA的雙重審批,拉長了資料移轉視窗。
遷移指令碼樣本
業務情境:將訂單表中訂單狀態為2和3的訂單狀態更新為4
backup
create table order_bak like order;insert into order_bak select * from order where status in (2,3);
註:order為業務表,order_bak為備份表;目標是將訂單狀態為3的訂單更新為4。migration
update order set status = 4 where id in (select id from order_bak);
註:如果直接對order原表進行操作,一旦錯誤,無法復原。
check
select count(*) as cont from order where status != 4 and id in (select id from order_bak);
註:如果cont > 1則需要考慮資料移轉是否成功
rollbak
update order o inner join order_bak bak on o.id = bak.id set o.status = bak.status;
註:如果遷移後-->復原前,有使用者行為更改status狀態,則不能直接rollback ,需要具體情況具體分析;實在避免不了,且此類資料很多,則考慮停服務遷移了。