異構資料庫遷移埋下的 9 個大坑怎麼躲開?

danny_2018發表於2022-03-08

“貳過”,重犯同一過失的意思,語出《論語》,完整句子為“不遷怒不貳過”,乃仲尼對其不幸早逝的弟子顏回的極高評價。就it專案而言,從管理者的角度來說,一個錯誤犯兩次,別說是同一個人了,哪怕是同一團隊的不同人也不可接受。說到這裡,不由得腦補出一位平時慈眉善目的老領導作嗔怒狀,大喊“幹掉幹掉”的恐怖場面。不重犯同一錯誤真的很難嗎,實話實說,確實難,君不聞,人類從歷史中得到的最大教訓就是從不吸取教訓。

筆者上述感嘆的背後是有故事的。那是本世紀最“2”的一天,筆者在排查Oracle到ADB(此處指ADB PG,全稱AnalyticDB PostgreSQL版,是阿里的MPP架構的資料庫)之間資料實時同步過程中,突然發現自己埋了個大雷——沒區分空值和空字串,導致同樣的欄位值在全量同步階段標誌為空字串,增量同步階段則標誌為空值,這顯然不是一種合乎邏輯的行為。

其實在發現這個問題前,筆者已經在這專案中苦苦掙扎了一個月,在此之前的兩天,筆者自以為資料質量已經完全沒問題,資料同步達到“又不是不能用”的境界了(筆者注:此處能用是真的能用,非為上線而上線那種能用)。Oracle DB本身確實不區分空值和空字串,對於搞Oracle出身的工程師,沒意識到這個似乎情有可原,然而筆者作為搬磚佬,已經前前後後搬了十幾年,資料庫異構遷移專案也認認真真地做過好幾個,這件事對筆者而言,其實無異於“低階錯誤”。

回想起來,這個專案筆者做得甚是狼狽,這裡面固然有進度緊以及新工具、新技術引入需要邊學邊用的客觀因素,但這不足以平息筆者對自己的憤怒。問題來了,在開源技術使用日益廣泛的今天,筆者也可能突然被要求用一個新工具同步資料到一個新資料庫,時間還可能更緊迫。到時怎麼辦呢?再憤怒一次嗎?不了不了,還是腳踏實地總結一下,記下這些坑,日後類似專案,哪怕被拿著槍指著頭也好,下述問題都要在前期階段予以考慮。

一、空值和空字串

對於Oracle DB⽽⾔,空值和空字串是同⼀回事,其中判斷某個值是否為空值/空字串統⼀使⽤"IS NULL"即可,⾄於=''是⽆效的。

這點對於其餘資料庫來說,可真不⼀定了。就源端為Oracle DB,⽬標端為其餘資料庫的項⽬⽽⾔,我們需要與開發商確認好,⽬標端究竟⽤哪種值表⽰源端的空值/空字元。⽽對於實時同步的項⽬來說,還需要在全量同步以及增量同步期間保持資料的⼀致。

除了空值和空字串以外,其實還有⼀個隱藏的⼤boss——固定⻓度型別中的空格值。以Oracle DB為例,char型別⽤於存放固定⻓度的字串,⽽⻓度不⾜的字串則會被⾃動補充空格,對於這種值,⽆論是char_col=''還是char_col is null均⽆法識別,只能透過trim(char_col) is null識別,對此值使⽤length函式會返回本欄位的⻓度,⼀個⾮0值。⽽這種資料同步到adb後則表現有⼀定差異了,char_col=''可以識別這種資料(哪怕ADB中也是⽤了固定⻓度的資料型別),然⽽對這個值使⽤char_length函式,結果會返回0,如果實在要把這種資料抓出來,我們還得加⼀個條件oct_length(char_col)>0。很明顯,這⼜是⼀個坑!

二、資料型別轉換問題

筆者的朋友阿強一直立志做一個溫文儒雅的人,嗯,前提是不要讓他“寫材料”、“擦屁股”以及“估算工作量”!沒錯,“估算工作量”對他這種以txt狂魔自稱的人來說是一種煎熬,光說起這個詞,他已經腦補出把pm按在地上,一邊揍一邊說,“我想做個好人,為什麼要逼我”?問題來了,如果讓阿強估算這個資料型別轉換的工作量,估計他連40米的刀都可以拿出來了。

跨資料庫的表欄位資料型別轉換工作主要涉及“精度”、“效率”、以及“相容性”等方面。部分資料同步軟體確實具備自動轉換的功能,然而這種情況,僅對於管理規範的資料庫適用,不規範的庫則需要耗費較大精力了,下面舉幾個典型的例子說明這個問題:

以從Oracle DB到PG的同步為例,整型的值固然可以透過number型別(不設定精度和小數位)存放到源端Oracle資料庫中,問題來了,PG中應該用啥欄位對應了,僅僅安全起見,免得精度丟失,那肯定是numeric型別,然而這毫無疑問存在效能損失。Bigint等型別自然是更好的選擇,可是這又牽涉到與開發商溝通這樣有沒有可能導致應用報錯等情況了,這又是一個工作量。

以從Oracle DB到DB2的同步為例,DB2有一個隱性要求,組成主鍵的欄位值前後不能包含空格,否則會被過濾掉,這樣會造成一些在源端Oracle資料庫本來就不相同的兩條記錄的在目標端DB2庫被誤判為同一條記錄進而引發資料衝突,影響資料同步。

不要問為什麼要在id類欄位加空格:第一,這是合法的;第二,這未嘗不是一種有創意的備份資料方法。這麼幹,真的,沒毛病!

以從Oracle DB到ADB的同步為例,由於MPP架構需要,我們需要額外指定DISTRIBUTED列。對於ADB而言,這裡還有一個附帶的要求,這個列應當為主鍵的一部分。

以Oracle DB到HBase的同步為例,HBase是強制要求有主鍵的,否則不能同步。之前的專案中,筆者被迫無奈拿Oracle的轉換後的ROWID作為HBase的rowkey,滿足其同步前置條件。

為什麼是轉換後?這又是另一個故事了,這裡就不展開了,只提示個關鍵詞,“預分割槽”。

嗯嗯,這明顯是個坑。那種由開發商定表結構的專案得心存感激,真的!

三、字符集轉換問題

字符集轉換深究起來其實並不是容易的事情,幾年前筆者所參與的一個O2O同步(即Oracle到Oracle的同步,下同) 遷移專案中,涉及了BIG5到UTF8的轉換,當時的同步工具為OGG。這個專案中各種亂七八糟的資料至今仍然對筆者歷歷在目,當然,這也讓筆者能更有經驗地處理異構資料庫同步中的字符集轉換問題。

跨字符集轉換的工作其實陷阱不少,因此,條件允許的話,筆者建議儘量規避。當然,遇到PG這種伺服器端不支援GBK,而源端Oracle DB偏偏是GBK的情況,那隻好迎難而上了,下面為筆者的建議:

涉及中文的情況,所見非所得,判斷一條中文記錄是否正常的依據,應該為其十六進位制編碼是否正常。對於Oracle DB而言,我們可以用dump函式,其餘DB需要找到類似的。

中文為多位元組字元,在不同的字符集下佔用的位元組數並不一致,典型例子為GBK為2位元組,UTF8為3位元組。目標端環境可能需要相應調整位元組寬度。

不同字符集所涵蓋的漢字是不一樣的,例如BIG5中就沒有“邨”字。

字符集中有一個“自定義”區域,如不進行特殊處理,有可能導致資料丟失。

“亂碼”會造成很大的困擾,對於Oracle DB而言,大致有如下幾種情況:

無法透過Oracle自帶convert函式轉換為目標庫編碼,以ADB為例,這部分資料會導致GPFDIST匯入失敗,進而影響資料全量同步。

可以透過Oracle自帶convert函式轉換為目標庫編碼,但無法重新轉換為原有資料,這部分資料會有潛在的資料丟失影響,對於遷移類專案需要重點關注是否涉及“自定義”字元區域。

含義不明的單位元組字元,如chr(0)、chr(255),這些字元往往夾雜在正常的字元中,以ADB為例,涉及dts( Data Transmission Service, 資料傳輸服務,系阿里的資料同步工具/服務)增量同步的話,相應記錄有資料一致性風險。

四、特殊字元處理

對於資料庫異構同步而言,特殊的字元,諸如單引號、雙引號、換行、斜槓、反斜槓等等也是一個困擾項,這一點在資料全量同步階段尤其明顯。

對於文字方式的全量資料同步來說,我們可以考慮下述幾種方式:

使用CSV格式;

使用多位元組分隔符;

進行資料清洗;

僅同步“正常”資料,“特殊”資料另行處理。

這些內容要說透,需要另外寫一篇文章了。

五、異常記錄處理

這裡的異常記錄,指的是那種本身就違反資料庫規範,不應該插入到資料庫中的記錄。

以Oracle DB為例,筆者遇到的記錄有異常日期格式以及異常數值兩類。

異常日期格式,典型例子有"0000-00-00 00:00:00"和"2022-02-30 00:00:00"。筆者在好幾個客戶環境都遇過這種資料,以至於筆者覺得這很“常見”,需要加到測試專案裡面。筆者這段時間做的Oracle到ADB同步專案確實遇到這種資料了,後者還造成dts的增量同步中斷,風險很高。所幸筆者的dts源端庫是基於OGG的目標庫部署的,Oracle自己的OGG工具也不能同步這種資料,這間接地擋了這部分異常的增量資料。在此基礎上,筆者只需要修復已有的異常資料即可,修復方法也很簡單,先+1再-1能修復大部分資料,至於不能修復的只能和業務協商重置一個值了。

異常數值型別,典型例子為NaN(即Not a Number),筆者僅在一個客戶環境中遇到,當時的場景為O2O同步 ,比較可怕的是連基本“來者不拒”的資料泵都無法同步這種資料。考慮到這個環境沒遇過這種資料,筆者這次偷懶了,沒做相應的測試。

六、全量同步測試

通常情況下,各種資料同步軟體都會自帶全量資料同步的功能,至於這個功能的效率、資源消耗、空間佔用等專案需要進行評估。如果其不能滿足需求,則可能需要考慮替代的手段。

在選取測試表的時候,筆者考慮綜合下面幾個因素選擇幾個測試表:

需要包括大表,大表往往是個瓶頸項;

需要囊括本次同步涉及表的欄位型別;

如果環境中存在中文等多位元組資料,則建議包含這種表;

建議找靜態的表或者準靜態進行測試,以方便核對資料一致性。

七、增量同步測試

作為資料同步專案,同步效率是一個重要因素,筆者建議在搭建完整的同步鏈路之前,拿資料變更頻繁的關鍵表進行測試,透過單表單程式的方式,剔除潛在的配置不當風險。

對於這方面,筆者建議如下:

儘量使用真實的資料;

筆者這次測試透過Ogg同步增量資料,比較切合生產實際變更情況,這種方式可以參考。

增量同步發起後,在目標資料庫後臺觀察對應的SQL語句。以筆者本次專案為例,這個階段發現了兩個問題:

由於大小寫敏感問題,dts目標側未成功識別出主鍵,導致所有欄位被加到where條件,影響效率,此問題後來透過修改同步配置解決。

筆者觀察到dts側雖然設定了高併發度,但實際執行中,僅個別程式工作,其餘處於空閒狀態,無法充分利用資源。此問題後來透過修改配置引數解決。

八、資料一致性測試

資料一致性又是一個可以另外寫一篇文章的話題,對此,筆者建議如下:

對比靜態或者準靜態的資料。很顯然,筆者這次使用的ogg中間庫方案很切合這個主題,如果沒這個,筆者只能透過停止同步程式後反查其停在哪個點,再用這個時間點做檢驗了。這個想法理論上可行,然而以筆者對dts的淺薄理解,這條路並不通,原因在於dts所停的時間點並不完全準確。

活用md5函式。大部分正經的資料庫均包含內建的md5函式(PS:無意內涵DB2,真不是故意的),這可以將一個複雜的字串簡化,以便用於運算確認兩端的資料一致性。

九、軟體侷限性

“越是漂亮的女人就越會騙人,記住啊!”

“不光是漂亮的女人不能相信,連貌似忠良的男人也不能相信。”

我覺得這段對話充分展示了一個產品的售前與售後的結局——殊途同歸。對於售前來說,拼指標、造場景、講故事等等手段都是為了證明我家產品很棒,快來買買買;就售後而言,找到產品的痛點,予以規避,以達到保證工作順利開展,避免一口大鍋從天而降的目的。大家都是靠博弈而生的,沒什麼兩樣,手裡的牌均是對技術的瞭解。

扯遠了,回到it專案中,異構資料庫的同步往往是邏輯的同步方式,這種方式必然有各種瓶頸的。對售後來說,再怎麼謾罵售前“管殺不管埋”也無濟於事,最現實的做法莫過於:找到短板,透過改善流程、最佳化需求甚至協同開發商改造應用的方式保證軟體的穩定執行。

這裡先講個故事。Timesten是Oracle的記憶體資料庫,其Cachegroup功能可以實現從物理庫(即Oracle DB)到記憶體庫的實時資料同步,而這個同步延遲對業務穩定執行是非常關鍵的。在實際使用中,運維人員總結出的經驗就是得規避大事務變更,最終他們與開發商達成相應的操作規範,無論是業務變更需求也好,資料庫運維發起的清理作業也罷,如涉及Timestens同步的表,都得遵循變更量達10w萬就得分批提交,每個批次2萬條記錄,每批次之間sleep 30秒的硬性規定。我覺得這個故事的結局很完滿了,真的,要是換成非得揪著Timesten不放,意圖純粹靠軟體解決問題的話,那才是妥妥的災難現場呢,畢竟其基於trigger的同步機制從原理上就對大事務極不友好……

問題來了,如何找到軟體的短板呢?

閱讀官方文件自然是一個渠道,當然,閱讀也是有“技巧” 的 :

我們支援xx指標以內的場景,這句話可以理解為超過這個您就得想想辦法了,同時,這個值也許是要打個折的,畢竟環境不一樣,存在差異也是很合理的。

我們支援功能a,也支援功能b,這都是實話,至於同時支援功能a和b是您自己認為的,我可沒說。

這個嘛,春秋筆法是有的,這種玩法自古就有了。陳壽不也沒在《三國志》裡面明說司馬昭弒君嗎,後來大家不都知道了嗎?

除了文件以外,我們還可以考慮結合自身經驗考慮下述點。

大事務測試

分別對同步範圍內外的物件做批次操作,加大資料庫日誌量,觀察其對資料同步以及系統的影響,具體包括cpu、記憶體、io、空間等資源消耗以及同步延遲等。

以dts為例,源端oracle資料庫產生的所有資料均會被拉到dts的庫中分析,哪怕這資料與我們的同步策略無關。

目前有個黑名單功能可以繞過這問題。

長事務測試

包含啟動增量同步前開啟的事務能否正常同步、長時間未提交的事務是否影響同步程式重啟等維度。

很明顯, 這是被ogg嚇到的結果。

頻繁事務測試

筆者曾在O2O同步環境中遇到某應用使用了大量with as語法,後者隱式開啟了大量的短事務,進而短時間內事務量暴漲,進而 導致同步軟體Ogg抽取程式出現延遲。這個問題後來找開發商修改語句就解決了,然而其對筆者的心理陰影一直都在,以至於每遇到一個新場景,均會想想會不會遇到類似的問題。

事務順序

這個探究的是,軟體同步是否能保持事務的順序,如不能保持,那就得多留個心眼了,這種情況輕則導致同步延遲誤判,重則導致舊 資料覆蓋新資料,影響資料一致性。

一個簡單的測試樣例為,建立一個週期性(如每分鐘)更新的時間戳表,這個表的記錄數與源端Oracle DB的節點數一致。定時指令碼依次連線各個例項並以當前時間更新相應的欄位,在目標端,我們可以透過查詢時間點表觀察會不會出現下面兩種情況:

後更新的資料是否會先被查詢出來;

同步出現延遲的話,時間戳表記錄的時間會不會與同步程式的時間戳保持一致。

批次ddl測試

對於基於資料庫日誌的同步工具,大批次的DDL語句很可能會觸發源端解析緩慢的情況,畢竟這涉及與資料字典的互動。

筆者曾遇過某個基於Ogg的O2O同步環境遇到這種瓶頸,而最終的解決方案為調整開發商版本上載的指令碼,加大DDL語句之間等待時間間隔。

基於同步原理短板的探究

對於基於rowid的同步方案,沒啥好說的,只能從規範上減少move、shrink等改變rowid操作,實在要操作的話,需要重新同步相應的資料。

對於基於主鍵的同步方案,則重點考慮如何處理無主鍵表如何處理。

程式重啟測試

包含兩種場景,正常重啟以及異常重啟。

異常重啟即高可用方面的,具體不展開了;至於正常重啟的話,需要觀察程式的一些自定義引數會不會被重置。

作者介紹

黎君原,新炬網路架構師。

來自 “ dbaplus社群 ”, 原文作者:黎君原;原文連結:https://mp.weixin.qq.com/s/3olz2NMJIKEM448NC1W5aA,如有侵權,請聯絡管理員刪除。

相關文章