.NET+PostgreSQL實踐與避坑指南

來源:互聯網
上載者:User

標籤:軟體   exist   兩種   層級   自動   線程   tar   ...   生產   

簡介

.NET+PostgreSQL(簡稱PG)這個組合我已經用了蠻長的一段時間,感覺還是挺不錯的。不過大多數人說起.NET平台,還是會想起跟它“原汁原味”配套的Microsoft SQL Server(簡稱MSSQL),其實沒有MSSQL也沒有任何問題,甚至沒有Windows Server都沒問題,誰說用.NET就一定要上微軟全家桶?這都什麼年代了……

PG和MSSQL的具體比較我就不詳細展開了,自行搜一下,這種比較分析文章很多。應該說兩個RDBMS各有特色,MSSQL工具集龐大(大多我們都用不到或不會用),安裝較為麻煩,PG比較小巧,但功能也不弱,我們要的它都有,效能方面我做過簡單的增刪查改的測試,兩者看不出什麼明顯差別,MSSQL貌似最近才提供了Linux版,而PG天生跨平台,MSSQL的授權費似乎不低(沒深究),PG開源免費,對比較摳的客戶來說,是不太願意另外花錢買一套MSSQL的,PG就是非常不錯的選擇。

希望你看完本文之後,也同我一樣覺得.NET + PostgreSQL,Rocks!沒問題的了。

PG的版本

PG應該選擇什麼版本?Linux還是Windows?當然是首選Linux,但開發環境無所謂,你在你自己的工作電腦上安裝一個Windows版也是沒問題的,有人說兩者效能差距較大,Linux明顯要好於Windows,但我有做過測試,這個並沒有被證實如此,然而,我還是推薦Linux,一來安裝簡便,二來配置簡單(命令列介面用起來感覺比較一致),三來方便寫一些指令碼來實現資料庫定時備份之類的。其實你並不需要擔心安裝了PG後電腦會變慢,我完全感覺不出來,它是個安靜的乖萌寵,你不叫它,它就靜靜坐在那裡,我的Windows電腦上也安裝了一個PG,我經常用它來做一些指令碼測試或實驗。另外,現在也能在Windows下直接安裝Linux版本的PG了,WSL瞭解下?

PG有很多的版本,現在的最新版是10.4,它前面的版本是9.6.x,嗯?有點奇怪不是?10.4隻有“兩段”,而9.6.x有三段,其實之前一直是三段,9表示大版本,6表示中版本,後面是小版本,小版本只有小的功能改進,不會對資料格式造成任何影響,就是說,你的PG從9.6.1升級到9.6.9,你直接升了把舊程式替換掉就是,保證沒有任何問題。但如果你之前的版本是9.5.3,要升級到9.6.9,那就不行了,因為中間版本變了,你需要用一個遷移工具去把你的舊的資料格式轉為新的方可,那對10.4這個版本而言,哪個是大版本,哪個是中版本,哪個是小版本?這裡我感覺有點不連貫,PG在從9升級到10的時候,似乎丟掉了“大版本”,10雖然是9的後繼,但它應該算一個中版本,所以,10.1升級到10.4是不用轉換資料的,直接升級程式即可。那PG的下一個中版本是什嗎?沒錯,是11,再下一個應該就是12了。軟體這個東西,如果你沒什麼曆史包袱,我覺得直接選擇最新的,比如選擇10.4,將來升級10.5,10.6的時候也簡單。

說點額外的,PG10是去年(2017)正式推出的,距離現在都不到一年,剛出來的時候我就想,這個“重大升級”(想想看iPhone X,Mac OS X,10這個數字是很特別不是?)能不能帶來效能上的大提升呢?我試了一下,結論是:沒有。確實它的升級文檔上也沒提及到效能有什麼明顯提升,它主要增加了對錶分區的原生支援,表分區,就是你的表中的資料的數量很多很多的時候,通過表分區來提高讀寫速度,至於表要多大才推薦分區呢?PG的官方文檔說是:如果表的尺寸趕上了你主機的記憶體的時候,可以考慮表分區……所以,對於那些只有區區幾千萬行或幾百萬行資料的表,你確定要分區嗎?

Npgsql

要用.NET使用PG,就得用nuget引入Npgsql這個包,這是它的官方網站:http://www.npgsql.org/,完全開源,它其實就是針對PG資料庫的ADO.NET引擎(ADO.NET Data Provider)。這裡是它的協助手冊:http://www.npgsql.org/doc/index.html

這裡邊並沒有太多痛點,你所需要做的,就是安裝好你的PG資料庫(Windows版/Linux版都行,沒有什麼影響),然後建立一個.NET項目(我推薦使用.NET Core),引入Npgsql,然後照著說明手冊上的簡單例子入一下門即可。

本文當然不會具體帶你如何開始使用SELECT語句,下面主要講述在使用過程中,我們所克服的一些困難或踩過的坑。

NVARCHAR呢?

MSSQL中用得最多的的文本類型是NVARCHAR,這是一個帶長度限制的文本類型,對應地,PG中有VARCHAR,這樣用沒問題,但PG中的文本類型其實跟MSSQL中的文本類型是有點區別的,PG的文本基本上可以認為不限長度,VARCHAR及TEXT對PG內部來說,並沒有什麼差別,只是在寫入的時候,VARCHAR會檢查一下長度,所以效能上來看,VARCHAR並不比TEXT要快,較真的話可能還會慢點,因為它要檢查長度嘛,所以你在設計資料庫的時候可以無腦地將所有文本類型設定為TEXT(或後面提到的CITEXT),長度檢查工作放在業務系統中去做即可。

想要大小寫不敏感怎麼辦?

絕大多數時候,我們都是希望大小寫不敏感的,大小寫敏感反倒會帶來很多困惑,查詢不出,或者系統中存在同名的使用者,一個叫John另一個叫john,MSSQL可以在建立庫的時候指定大小寫不敏感,而PG似乎沒有這樣的功能,它需要藉助一個額外的組件,叫CITEXT,CI的意思就是Case Insensitive。要使用CITEXT組件,你需要安裝postgresql10-contrib包(假設你安裝的是PG10,如果不是的話你去找對應的包),再使用以下命令建立CITEXT類型:

CREATE EXTENSION IF NOT EXISTS CITEXT WITH SCHEMA public;

註:一個database只需要執行一次這個命令即可

如果你使用的是psql用戶端連上去使用PG的話,這時候已經OK了,你會發現CITEXT的欄位已經是大小寫不敏感了,但如果你用的是Npgsql用代碼去訪問PG的話,CITEXT似乎沒生效,其實原因是這樣的,CITEXT並不是PG的原生類型,你在用查詢語句的時候,需要在參數後面加上“::CITEXT”顯式地告訴PG,你的參數是CITEXT類型,例子如下:

SELECT * FROM test_table WHERE test_name=@TextName::CITEXT AND category=@Category::CITEXT

嗯,我承認是有點麻煩,但習慣就好,我現在還不知道有什麼更佳方法。

使用CITEXT時候出現NotSupportedException

這個異常的呈現內容大致如此:

System.NotSupportedException: The field ‘application_id‘ has a type currently unknown to Npgsql (OID 41000). You can retrieve it as a string by marking it as unknown, please see the FAQ.在 Npgsql.NpgsqlDataReader.GetValue(Int32 ordinal)在 Npgsql.NpgsqlDataReader.get_Item(Int32 ordinal)……

這個錯誤對我們而言,曾經像個幽靈似的,時不時出現,出現的時候重啟一下服務程式就好了,不再出現,然後過幾個星期或者幾個月又出現,有時候一天出現多次也不是沒有可能。最後是到github上面求助才最終搞懂了原因。連結:https://github.com/npgsql/npgsql/issues/1635

簡單地說,PG對各種資料類型,是有一個內部的ID值的(叫oid),Npgsql在第一次串連資料庫的時候,會擷取到這些oid值並緩衝起來,對於PG的內部類型,如INT什麼的,這些oid值是固定的,但對於CITEXT似乎不是這樣,因為CITEXT這個類型是我門自己用CREATE EXTENSION命令建立的(請參考本文前面內容),建立的時候確定其oid。我們在還原資料庫的時候,也相當於重新建立了CITEXT類型,這樣會導致CITEXT的oid發生變化,但Npgsql並不知道,所以就出現了這個異常。我們在開發過程中常常需要做還原資料庫的動作,所以導致了這個問題的發生。

解決方案1,當資料庫還原了之後,調用NpgsqlConnection.ReloadTypes(),重新整理各類型oid,但這個很難,因為還原資料庫都是手動操作,做完之後開啟網頁,在上面點一下通知程式嗎?

解決方案2,重啟一下程式。這個其實跟解決方案1差不多,只不過不需要寫什麼額外代碼,考慮到還原資料庫這個動作其實也不是太頻繁,只是在開發環境中做,所以重啟就重啟吧,我們現在就幹,規定還原資料庫後自己重啟下服務程式。(寫個指令碼幹這個事情很簡單)

使用事務進行大量操作時候導致程式崩潰

這個問題我同樣到github上求助了,連結:https://github.com/npgsql/npgsql/issues/1838

這個問題比前面的問題可能更嚴重,因為我很可能捕捉不到異常(就是說有時候可以捕捉到,有時候不行),程式直接崩潰了,對於一個.NET程式來說,這是很不應該的事情,即便我沒單獨寫try-catch,程式的最外層異常處理器應該也能捕捉到相關的Exception並log對不?但偏不,沒有log,也捕捉不到。所以至今我懷疑這是一個.NET的bug,可能跟Npgsql並沒有關係。

問題的原因如github上所描述,是找到了,但卻無法從根本上修正,這個問題其實是個簡單的“事務逾時”問題。

我們的程式在第一次啟動的時候會初始化資料庫的表,插入大量的初始化資料,由於我們公司的開發環境比較特殊,資料庫延遲十分高,所以導致插入速度很慢,每條插入耗時可高達幾十毫秒,(生產環境並沒有這個問題)這樣一萬多條資料下來就導致了事務逾時(事務逾時預設時間是1分鐘)。解決方案當然很明顯了:初始化的時候,臨時增加 TransactionScope的逾時值,增加到10分鐘,這樣總歸沒問題了。

類似這種問題我們只能通過一些外部的workaround來預防,很難從根本上解決。

55000: 禁用已準備好的事務

這又是一個有點棘手的事情,首先是這個中文翻譯得很不好,這是一條資料庫拋出來的出錯資訊,它的英文是“Prepared transactions are disabled”,其正確的中文翻譯我覺得應該是:預先處理事務已被禁用。唉,所以我說為什麼要英文版,如果提示中文,想在網上找答案都會多些障礙。

對事務的使用,這裡有個簡單的例子:

    using (NpgsqlConnection conn = new NpgsqlConnection(connectionStr)) {        conn.Open();        using (TransactionScope ts = new TransactionScope()) {            conn.EnlistTransaction(Transaction.Current);                //SQLs...            }            ts.Complete();        }    }

什麼叫“預先處理事務”?其實很簡單,就是“事務包事務”,就是可以分步提交的事務,比如我先開啟了一個事務A,在這個事務中我又開啟了一個事務B,B提交,A再提交。PG對於預先處理事務是預設關閉的,當然了,你可以開啟它,編輯設定檔postgresql.conf,把max_prepared_transactions改為100(預設是0,0表示禁用),重啟PG服務即可。

但你確定你真的用得到預先處理事務嗎?我看下來我們是用不到的,但為什麼出現這個問題?——還是我們程式寫得有問題,即便你從單個方法上看不出來事務包事務。以下兩種情境可能會出現“預先處理事務”:

1,我建立了一個方法A訪問資料庫,這個方法可能會被其它方法調用,所以它有個DbConnection類型的參數,表示調用者負責開啟資料庫連接傳遞過來,而A裡面開啟了事務,而調用者並不知情,也開啟了事務,形成預先處理事務

2,這種情況更隱晦些,資料庫連接字串,如:Host=192.168.1.101; Username=postgres; Password=123456; Database=testdb; Enlist=true,在後面有個叫Enlist的參數為true,這表示這個串連在開啟的時候,會自動Enlist到當前執行內容的Transaction中去,如果當前執行內容中開啟了事務(從代碼上看包含在了using(TransactionScope)中),那這個資料庫連接就自動Enlist上去了,再考慮這樣的情境:A方法會自己開啟資料庫連接去查詢點什麼東西,B方法也會訪問資料庫,且B方法會使用事務,事務中調用了A方法,A方法開啟資料庫連接的時候發現當前執行內容中存在Transaction,於是自動Enlist上去了,不經意間形成了預先處理事務,且還是“分布式”的(A和B開啟的可能是不同的資料庫連接),這種情況應該並不是你所需要的

那我們應該怎麼做?下面是我的做法:

1,max_prepared_transactions還是設定為0,關掉,因為我們真用不到,如果用得到,那就是我們代碼寫錯了,所以一旦出現“禁用已準備好的事務”這個異常,就回去檢查代碼

2,把Enlist=true在資料庫連接字串中去掉,這麼一來,每次使用事務都需要顯式地調用 conn.EnlistTransaction(Transaction.Current),雖然對了一行代碼,但語義更明確,也不用考慮到底是TransactionScope包DbConnection或反過來DbConnection包TransactionScope

3,正常化我們的資料庫存取碼,明確哪些是需要事務哪些是不需要的,在各個方法的注釋上註明

40001:由於多個事務間的讀/寫依賴而無法串列訪問

它對應的英文是:Cound not serialize access due to read/write dependencies among transactions,這個應該怎麼理解呢?其實瞭解資料庫交易隔離等級的人對這個應該不會陌生。.NET的TransactionScope預設使用的是交易隔離等級中的最進階別——Serializable(可序列化)。這個層級最大程度上確保了資料的一致性,但代價也挺高,一來速度較慢,二來很容易出現“事務間讀/寫依賴”,就是這個錯誤了,舉個簡單的例子:

A、B兩個事務,同時訪問test表,各自插入100條資料,它們插入資料完畢後都需要sum一下這個表的總條數然後寫入另一張表中去,這就會出問題了,因為不管對A或者對B,他們在事務開啟的時候對方的事務都沒有commit,他們對test表的視圖其實是一個事務開始時刻的“快照”,而在事務提交前,它們insert的資料對對方其實是不可見的,所以如果讓A和B這兩個事務都成功的話,sum出來的資料肯定會有不正確的,這就所謂“資料不一致”。所以資料庫會讓A、B中的一個成功,而另一個失敗,並拋出這個異常。

所以,這是個“正常的錯誤”,按常規的商務邏輯來說,應該很少會出現,如果真的出現,且頻繁出現,那需要考慮下是不是商務邏輯設計得不太合理,看看能不能從設計上避免這個問題,如果商務邏輯一定如此,那可以用下面的方法嘗試一下:

1,將這種並行事務用用戶端代碼排個隊,弄個安全執行緒隊列,逐個執行,這樣速度會慢點,但確保了每個事務都能成功

2,捕捉這個異常,然後自動重試,其實這也是資料庫推薦的正統的做法

3,降低交易隔離等級,這個可能會出現問題,也可能不出現,這完全取決於你的業務,關於交易隔離等級,這是個蠻大的話題,我考慮適當時候再寫一篇文章

4,對於極少出現的頻次來說,可以不處理,僅僅需要捕捉這個異常類型,然後提示使用者重試即可,很多網站貌似都這麼乾的

總結

有時間的話我會另外開一篇文章來寫寫PG的一些常規用法,如熱備冷備還原維護等,但不太能保證什麼時候能寫出來。

暫時先總結那麼多,誰如果有這方面的問題的話,歡迎留言。

.NET+PostgreSQL實踐與避坑指南

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.