被一個貌似簡單的老技術問題虐了幾天

公司的程序須要使用一套外部系統,是必需要用,沒得商量的那種,這套外部系統Since 2001年,有將近18年的歷史,跟它對接用什麼方式?說出來你可能不信,居然是用生成Microsoft Access(簡稱MSAccess)文件的方式,沒錯,就是生成mdb文件,往mdb裏寫數據,而後丟給這套外部系統。
 
一個mdb,就是一個數據庫,文件型數據庫,這也是不少我這個年代來的程序員接觸的第一個數據庫系統,我記得最先的時候我是用VB(不是VB.NET,那時沒有.NET)來訪問它的。後來我發覺在比較簡單的應用場景裏,根本就不須要把數據保存到mdb中,直接讀寫帶必定格式的文件比mdb訪問快得多;再後來我發現能夠用一些壓縮算法來生成更小巧的數據存儲文件;再後來我發現xml是一種可讀性更好的數據存儲方式;再後來我發覺sqlite功能蠻強,不比MSAccess差,因爲開源跨平臺,應用場景更豐富……總而言之,我沒有理由再使用mdb來存儲數據了。
 
若干年後(若是今天看的話就是許多許多年前了),我去了一家公司,我所在的部門有一套很是重要的程序,用VC++寫的,保存數據的文件的格式居然就是mdb……VC++使用mdb文件的方式至關繁瑣且容易出錯,我當時以爲十分不爽,因而努力替換掉它,把它以前須要好幾兆空間保存的數據壓縮到幾十K,你沒看錯,縮小了兩個數量級,讀寫速度飛快,可這並非一件令我有多少愉快回憶的事情,你想我這麼一個新人努力地把人家辛辛苦苦定下來的東西「推倒重來」,人家會怎麼想?
 
又若干年後,命運又跟我開了個玩笑:對不起,雖然已是2019年了,你仍是得繼續經過mdb文件交換數據!我:……
 

 
如今公司的程序並不是2019年才誕生,早有了,使用.NET Framework訪問mdb文件比VC++輕鬆無數倍,雖然如此,我仍是想進一步簡化工做,我寫了一套幫助類庫,讓程序無需關心具體操做,只須要準備好數據到一個數據字典中,把數據字典交給個人幫助類庫,就自動完成了填充mdb文件的操做,很是方便。
 
在剛過去的2018年裏,我幹了一件事情,就是將大量的程序從.NET Framework上遷移至.NET Core,這裏邊天然遇到了很多障礙,其中一個「沒法逾越」的障礙就是mdb的生成!
 
訪問mdb數據庫的正統方法是OLEDB接口,OLE這是個古老的概念,它基於COM(公共組件模型),COM的一個重要特色就是:Windows特有,而且,微軟沒有把它作成跨平臺的打算。
 
我花了不少時間尋找解決方案,未果,看來只能使用一些折中的作法:mdb的生成使用一個公共的Windows服務器來作,我自定義了一套通訊規則,編寫了生成mdb的服務程序,還提供了一套方便的客戶端代碼,調用者能夠像原先那樣,直接一個方法生成mdb,不用關心中間通過了那些網絡通訊步驟,仍是同樣的方便。個人程序通過了全面的測試,確認無誤,一切看起來很是OK,直到系統正式使用的時候客戶反映mdb的生成頻頻出問題……
 
立刻查看log,發現了這麼一個錯誤:「XXXX正由另外一進程使用,所以該進程沒法訪問此文件。」XXXX即是我要生成的mdb文件,我在生成mdb文件的時候,把模板複製到臨時目錄一份,名字用GUID,不會重複的,打開,填充數據,關閉,而後將mdb文件返回給客戶端。這「正由另外一進程使用」是什麼意思?個人文件只多是我這個進程使用啊。
 
再通過了N次測試,我本地就是重現不出這個問題,只有把程序部署到服務器上纔會出現,而且不能每次都重現,個人本地和服務器的差異是IIS Express和IIS的差異,但對IIS這麼紛繁複雜的配置,我感受要從中找出什麼端倪來簡直是大海撈針,搜索相關信息,無果。(不喜歡Windows服務器很大程度上是不喜歡IIS,對,我就這麼旗幟鮮明)
 
這個問題的本質就是:我關閉了OLEDB的鏈接後,mdb文件彷佛還處於鎖定狀態,想用程序讀取這個mdb文件返回的話就會出錯。好,這個難不倒我,我開始尋找一些解決方法。 

1,禁用鏈接池

 OLEDB數據庫鏈接默認也是有鏈接池的,你關閉一個數據庫鏈接,實際上並非真的關閉它,而是讓它返回到池中,準備給下次打開鏈接時使用。我很快找到了禁用鏈接池的選項,禁用之,測試,OK,上線!問題依舊。

2,GC.Collect

 我雖然開始學.NET的時候就知道垃圾回收,可直接顯式地使用這個方法,是第一次,按照微軟官方文檔的說法,通常狀況下並沒有GC.Collect的必要,調用這個方法能強制釋放一些內存資源,也許能強迫數據庫真正關閉。但上線後很快發現問題依舊。

3,外部複製

通過嘗試,我發覺居然能夠用Windows的copy命令來複制被打開着的mdb,我靈光一閃,對,服務器也能夠這麼幹,因而關閉mdb鏈接後調用了外部的copy命令,複製生成的mdb文件,再把複製好的這個文件返回給客戶端,perfect!果真,客戶那邊再也不反映什麼問題了。這個難題難道就這麼解決了嗎?圖樣!次日,客戶找到咱們,說咱們生成的mdb文件缺數據!這是大問題!甚至比生成不了mdb的問題還要打,前者的話他們能夠再次嘗試,然後者則可能帶來業務上的數據錯誤。我立刻到服務器上看,對比了生成的臨時文件,我確認了問題還在!我這樣copy被鎖定的mdb文件是未寫入徹底的!有緩存,我要想方法禁掉緩存。

4,嘗試禁用OLEDB的寫入緩存

Windows在將數據存儲至磁盤上的時候,其實都不是直接寫磁盤,而是使用了一種緩存機制,先寫到緩存,再緩存到物理磁盤,這樣無疑提升了調用的速度,因此纔有了Flush的概念,Flush就是當即將緩存的數據沖掉,也就是強制當即寫入磁盤,而我如今要作的,是禁用掉這個緩存,讓對mdb的操做更直截了當地寫到磁盤上,我費了很多力氣,在微軟的官方文檔上找到了這個選項,可是這回更慘,本地測試報錯,緣由是並不支持這個選項,能夠理解爲OLEDB只是接口,究竟支不支持,還得看引擎,訪問mdb的這個Jet引擎貌似不行。

5,更換引擎

Microsoft.Jet.OLEDB.4.0變動爲Microsoft.ACE.OLEDB.12.0,嗯,具體的安裝文件能夠在微軟官網下載到。ACE.OLEDB.12.0這個應該更新一些,結果仍是不行,而且同樣地不支持上一點提到的那個選項。

6,等待解鎖

 既然上面都不是辦法,那我就用重試的機制,讀取文件失敗,拋出異常,捕捉異常,間隔兩秒鐘,重試讀取,再失敗,再重試,最多嘗試兩分鐘,兩分鐘文件總歸寫入完成了吧?結果仍是不行,2分鐘仍是沒法讀取,且這回客戶那邊有些受不了這樣的漫長等待了,客戶問能不能恢復以前那個有數據缺失的版本?至少能用。

7,到底有沒有.NET Core下的訪問mdb文件的第三方庫

微軟不提供支持,我找第三方行不行?很顯然,我以前找過,但此次我打算更認真仔細地找。最後找到了這麼一篇文章:《 Using Microsoft Access in .NET Core》,寫於2018年11月,還挺新,難怪我以前沒找到。文章提到了,.NET Core能夠用ODBC接口訪問mdb,但微軟只提供了Windows版本的ODBC引擎,Linux版本的卻沒有提供,想在Linux環境下直接用ODBC訪問mdb的話得找第三方的方案,這裏就有一個: Access ODBC Driver,但並難免費,而且費用還不低。
 
看吧,老闆是確定不會贊成讓我去買這麼一個東西的。

8,ODBC

 好,既然上面提到ODBC,那我就切換到ODBC接口去,不用什麼OLEDB了,過程進展還挺順利,除了對MSAccess的類型的理解有少量不一樣外,ODBC和OLEDB接口差異很小,我很快就弄完了。測試,上線!嗯?此次竟然貌似能夠了。客戶用了一天下來,沒再發現什麼異常,我檢查了日誌,也沒再出現「正由另外一進程使用」的問題。此次難道真的好了嗎?我已經有點焦頭爛額了。
 
好吧!至少到如今沒再出什麼問題。儘管我這幾天裏並不僅在處理這個問題,但這個問題倒是這幾年來最讓我掉面子的問題了。第一它很老,第二它看起來很簡單,第三它彷佛有不少種變通方法。但實際搞起來卻被它虐了許多回合,實在使人唏噓。
 

 
半年前一個許多年前的老同事忽然QQ我,說他在解決一個技術問題的時候搜到了個人博客,因而想起了我,挺懷念當初跟我學技術的日子,由於2018年了,他竟然在作VB,沒錯,不是VB.NET,是VB,本文開頭提到的那個VB,這是一套極其古老的系統,他還在維護着,我能理解他的苦悶,但這就是生活。
相關文章
相關標籤/搜索