域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過(guò)
這篇文章主要介紹了.NET+PostgreSQL實(shí)踐與避坑指南,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
簡(jiǎn)介
.NET+PostgreSQL(簡(jiǎn)稱PG)這個(gè)組合我已經(jīng)用了蠻長(zhǎng)的一段時(shí)間,感覺(jué)還是挺不錯(cuò)的。不過(guò)大多數(shù)人說(shuō)起.NET平臺(tái),還是會(huì)想起跟它“原汁原味”配套的Microsoft SQL Server(簡(jiǎn)稱MSSQL),其實(shí)沒(méi)有MSSQL也沒(méi)有任何問(wèn)題,甚至沒(méi)有Windows Server都沒(méi)問(wèn)題,誰(shuí)說(shuō)用.NET就一定要上微軟全家桶?這都什么年代了……
PG和MSSQL的具體比較我就不詳細(xì)展開(kāi)了,自行搜一下,這種比較分析文章很多。應(yīng)該說(shuō)兩個(gè)RDBMS各有特色,MSSQL工具集龐大(大多我們都用不到或不會(huì)用),安裝較為麻煩,PG比較小巧,但功能也不弱,我們要的它都有,性能方面我做過(guò)簡(jiǎn)單的增刪查改的測(cè)試,兩者看不出什么明顯差別,MSSQL貌似最近才提供了Linux版,而PG天生跨平臺(tái),MSSQL的授權(quán)費(fèi)似乎不低(沒(méi)深究),PG開(kāi)源免費(fèi),對(duì)比較摳的客戶來(lái)說(shuō),是不太愿意另外花錢買一套MSSQL的,PG就是非常不錯(cuò)的選擇。
希望你看完本文之后,也同我一樣覺(jué)得.NET + PostgreSQL,Rocks!沒(méi)問(wèn)題的了。
PG的版本
PG應(yīng)該選擇什么版本?Linux還是Windows?當(dāng)然是首選Linux,但開(kāi)發(fā)環(huán)境無(wú)所謂,你在你自己的工作電腦上安裝一個(gè)Windows版也是沒(méi)問(wèn)題的,有人說(shuō)兩者性能差距較大,Linux明顯要好于Windows,但我有做過(guò)測(cè)試,這個(gè)并沒(méi)有被證實(shí)如此,然而,我還是推薦Linux,一來(lái)安裝簡(jiǎn)便,二來(lái)配置簡(jiǎn)單(命令行界面用起來(lái)感覺(jué)比較一致),三來(lái)方便寫一些腳本來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)定時(shí)備份之類的。其實(shí)你并不需要擔(dān)心安裝了PG后電腦會(huì)變慢,我完全感覺(jué)不出來(lái),它是個(gè)安靜的乖萌寵,你不叫它,它就靜靜坐在那里,我的Windows電腦上也安裝了一個(gè)PG,我經(jīng)常用它來(lái)做一些腳本測(cè)試或試驗(yàn)。另外,現(xiàn)在也能在Windows下直接安裝Linux版本的PG了,WSL了解下?
PG有很多的版本,現(xiàn)在的最新版是10.4,它前面的版本是9.6.x,嗯?有點(diǎn)奇怪不是?10.4只有“兩段”,而9.6.x有三段,其實(shí)之前一直是三段,9表示大版本,6表示中版本,后面是小版本,小版本只有小的功能改進(jìn),不會(huì)對(duì)數(shù)據(jù)格式造成任何影響,就是說(shuō),你的PG從9.6.1升級(jí)到9.6.9,你直接升了把舊程序替換掉就是,保證沒(méi)有任何問(wèn)題。但如果你之前的版本是9.5.3,要升級(jí)到9.6.9,那就不行了,因?yàn)橹虚g版本變了,你需要用一個(gè)遷移工具去把你的舊的數(shù)據(jù)格式轉(zhuǎn)為新的方可,那對(duì)10.4這個(gè)版本而言,哪個(gè)是大版本,哪個(gè)是中版本,哪個(gè)是小版本?這里我感覺(jué)有點(diǎn)不連貫,PG在從9升級(jí)到10的時(shí)候,似乎丟掉了“大版本”,10雖然是9的后繼,但它應(yīng)該算一個(gè)中版本,所以,10.1升級(jí)到10.4是不用轉(zhuǎn)換數(shù)據(jù)的,直接升級(jí)程序即可。那PG的下一個(gè)中版本是什么?沒(méi)錯(cuò),是11,再下一個(gè)應(yīng)該就是12了。軟件這個(gè)東西,如果你沒(méi)什么歷史包袱,我覺(jué)得直接選擇最新的,比如選擇10.4,將來(lái)升級(jí)10.5,10.6的時(shí)候也簡(jiǎn)單。
說(shuō)點(diǎn)額外的,PG10是去年(2017)正式推出的,距離現(xiàn)在都不到一年,剛出來(lái)的時(shí)候我就想,這個(gè)“重大升級(jí)”(想想看iPhone X,Mac OS X,10這個(gè)數(shù)字是很特別不是?)能不能帶來(lái)性能上的大提升呢?我試了一下,結(jié)論是:沒(méi)有。確實(shí)它的升級(jí)文檔上也沒(méi)提及到性能有什么明顯提升,它主要增加了對(duì)表分區(qū)的原生支持,表分區(qū),就是你的表中的數(shù)據(jù)的數(shù)量很多很多的時(shí)候,通過(guò)表分區(qū)來(lái)提高讀寫速度,至于表要多大才推薦分區(qū)呢?PG的官方文檔說(shuō)是:如果表的尺寸趕上了你主機(jī)的內(nèi)存的時(shí)候,可以考慮表分區(qū)……所以,對(duì)于那些只有區(qū)區(qū)幾千萬(wàn)行或幾百萬(wàn)行數(shù)據(jù)的表,你確定要分區(qū)嗎?
Npgsql
要用.NET使用PG,就得用nuget引入Npgsql這個(gè)包,這是它的官方網(wǎng)站:http://www.npgsql.org/,完全開(kāi)源,它其實(shí)就是針對(duì)PG數(shù)據(jù)庫(kù)的ADO.NET引擎(ADO.NET Data Provider)。這里是它的幫助手冊(cè):http://www.npgsql.org/doc/index.html
這里邊并沒(méi)有太多難點(diǎn),你所需要做的,就是安裝好你的PG數(shù)據(jù)庫(kù)(Windows版/Linux版都行,沒(méi)有什么影響),然后創(chuàng)建一個(gè).NET項(xiàng)目(我推薦使用.NET Core),引入Npgsql,然后照著說(shuō)明手冊(cè)上的簡(jiǎn)單例子入一下門即可。
本文當(dāng)然不會(huì)具體帶你如何開(kāi)始使用SELECT語(yǔ)句,下面主要講述在使用過(guò)程中,我們所克服的一些困難或踩過(guò)的坑。
NVARCHAR呢?
MSSQL中用得最多的的文本類型是NVARCHAR,這是一個(gè)帶長(zhǎng)度限制的文本類型,對(duì)應(yīng)地,PG中有VARCHAR,這樣用沒(méi)問(wèn)題,但PG中的文本類型其實(shí)跟MSSQL中的文本類型是有點(diǎn)區(qū)別的,PG的文本基本上可以認(rèn)為不限長(zhǎng)度,VARCHAR及TEXT對(duì)PG內(nèi)部來(lái)說(shuō),并沒(méi)有什么差別,只是在寫入的時(shí)候,VARCHAR會(huì)檢查一下長(zhǎng)度,所以性能上來(lái)看,VARCHAR并不比TEXT要快,較真的話可能還會(huì)慢點(diǎn),因?yàn)樗獧z查長(zhǎng)度嘛,所以你在設(shè)計(jì)數(shù)據(jù)庫(kù)的時(shí)候可以無(wú)腦地將所有文本類型設(shè)置為TEXT(或后面提到的CITEXT),長(zhǎng)度檢查工作放在業(yè)務(wù)系統(tǒng)中去做即可。
想要大小寫不敏感怎么辦?
絕大多數(shù)時(shí)候,我們都是希望大小寫不敏感的,大小寫敏感反倒會(huì)帶來(lái)很多困惑,查詢不出,或者系統(tǒng)中存在同名的用戶,一個(gè)叫John另一個(gè)叫john,MSSQL可以在創(chuàng)建庫(kù)的時(shí)候指定大小寫不敏感,而PG似乎沒(méi)有這樣的功能,它需要借助一個(gè)額外的組件,叫CITEXT,CI的意思就是Case Insensitive。要使用CITEXT組件,你需要安裝postgresql10-contrib包(假設(shè)你安裝的是PG10,如果不是的話你去找對(duì)應(yīng)的包),再使用以下命令創(chuàng)建CITEXT類型:
CREATE EXTENSION IF NOT EXISTS CITEXT WITH SCHEMA public;
注:一個(gè)database只需要執(zhí)行一次這個(gè)命令即可
如果你使用的是psql客戶端連上去使用PG的話,這時(shí)候已經(jīng)OK了,你會(huì)發(fā)現(xiàn)CITEXT的字段已經(jīng)是大小寫不敏感了,但如果你用的是Npgsql用代碼去訪問(wèn)PG的話,CITEXT似乎沒(méi)生效,其實(shí)原因是這樣的,CITEXT并不是PG的原生類型,你在用查詢語(yǔ)句的時(shí)候,需要在參數(shù)后面加上“::CITEXT”顯式地告訴PG,你的參數(shù)是CITEXT類型,例子如下:
SELECT * FROM test_table WHERE test_name=@TextName::CITEXT AND category=@Category::CITEXT
嗯,我承認(rèn)是有點(diǎn)麻煩,但習(xí)慣就好,我現(xiàn)在還不知道有什么更佳方法。
使用CITEXT時(shí)候出現(xiàn)NotSupportedException
這個(gè)異常的呈現(xiàn)內(nèi)容大致如此:
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)
……
這個(gè)錯(cuò)誤對(duì)我們而言,曾經(jīng)像個(gè)幽靈似的,時(shí)不時(shí)出現(xiàn),出現(xiàn)的時(shí)候重啟一下服務(wù)程序就好了,不再出現(xiàn),然后過(guò)幾個(gè)星期或者幾個(gè)月又出現(xiàn),有時(shí)候一天出現(xiàn)多次也不是沒(méi)有可能。最后是到github上面求助才最終搞懂了原因。鏈接:https://github.com/npgsql/npgsql/issues/1635
簡(jiǎn)單地說(shuō),PG對(duì)各種數(shù)據(jù)類型,是有一個(gè)內(nèi)部的ID值的(叫oid),Npgsql在第一次連接數(shù)據(jù)庫(kù)的時(shí)候,會(huì)獲取到這些oid值并緩存起來(lái),對(duì)于PG的內(nèi)部類型,如INT什么的,這些oid值是固定的,但對(duì)于CITEXT似乎不是這樣,因?yàn)镃ITEXT這個(gè)類型是我門自己用CREATE EXTENSION命令創(chuàng)建的(請(qǐng)參考本文前面內(nèi)容),創(chuàng)建的時(shí)候確定其oid。我們?cè)谶€原數(shù)據(jù)庫(kù)的時(shí)候,也相當(dāng)于重新創(chuàng)建了CITEXT類型,這樣會(huì)導(dǎo)致CITEXT的oid發(fā)生變化,但Npgsql并不知道,所以就出現(xiàn)了這個(gè)異常。我們?cè)陂_(kāi)發(fā)過(guò)程中常常需要做還原數(shù)據(jù)庫(kù)的動(dòng)作,所以導(dǎo)致了這個(gè)問(wèn)題的發(fā)生。
解決方法1,當(dāng)數(shù)據(jù)庫(kù)還原了之后,調(diào)用NpgsqlConnection.ReloadTypes(),刷新各類型oid,但這個(gè)很難,因?yàn)檫€原數(shù)據(jù)庫(kù)都是手動(dòng)操作,做完之后打開(kāi)網(wǎng)頁(yè),在上面點(diǎn)一下通知程序嗎?
解決方法2,重啟一下程序。這個(gè)其實(shí)跟解決方法1差不多,只不過(guò)不需要寫什么額外代碼,考慮到還原數(shù)據(jù)庫(kù)這個(gè)動(dòng)作其實(shí)也不是太頻繁,只是在開(kāi)發(fā)環(huán)境中做,所以重啟就重啟吧,我們現(xiàn)在就干,規(guī)定還原數(shù)據(jù)庫(kù)后自己重啟下服務(wù)程序。(寫個(gè)腳本干這個(gè)事情很簡(jiǎn)單)
使用事務(wù)進(jìn)行大量操作時(shí)候?qū)е鲁绦虮罎?/p>
這個(gè)問(wèn)題我同樣到github上求助了,鏈接:https://github.com/npgsql/npgsql/issues/1838
這個(gè)問(wèn)題比前面的問(wèn)題可能更嚴(yán)重,因?yàn)槲液芸赡懿蹲讲坏疆惓?就是說(shuō)有時(shí)候可以捕捉到,有時(shí)候不行),程序直接崩潰了,對(duì)于一個(gè).NET程序來(lái)說(shuō),這是很不應(yīng)該的事情,即便我沒(méi)單獨(dú)寫try-catch,程序的最外層異常處理器應(yīng)該也能捕捉到相關(guān)的Exception并log對(duì)不?但偏不,沒(méi)有l(wèi)og,也捕捉不到。所以至今我懷疑這是一個(gè).NET的bug,可能跟Npgsql并沒(méi)有關(guān)系。
問(wèn)題的原因如github上所描述,是找到了,但卻無(wú)法從根本上修正,這個(gè)問(wèn)題其實(shí)是個(gè)簡(jiǎn)單的“事務(wù)超時(shí)”問(wèn)題。
我們的程序在第一次啟動(dòng)的時(shí)候會(huì)初始化數(shù)據(jù)庫(kù)的表,插入大量的初始化數(shù)據(jù),由于我們公司的開(kāi)發(fā)環(huán)境比較特殊,數(shù)據(jù)庫(kù)延遲十分高,所以導(dǎo)致插入速度很慢,每條插入耗時(shí)可高達(dá)幾十毫秒,(生產(chǎn)環(huán)境并沒(méi)有這個(gè)問(wèn)題)這樣一萬(wàn)多條數(shù)據(jù)下來(lái)就導(dǎo)致了事務(wù)超時(shí)(事務(wù)超時(shí)默認(rèn)時(shí)間是1分鐘)。解決方法當(dāng)然很明顯了:初始化的時(shí)候,臨時(shí)增加 TransactionScope的超時(shí)值,增加到10分鐘,這樣總歸沒(méi)問(wèn)題了。
類似這種問(wèn)題我們只能通過(guò)一些外部的workaround來(lái)預(yù)防,很難從根本上解決。
55000: 禁用已準(zhǔn)備好的事務(wù)
這又是一個(gè)有點(diǎn)棘手的事情,首先是這個(gè)中文翻譯得很不好,這是一條數(shù)據(jù)庫(kù)拋出來(lái)的出錯(cuò)信息,它的英文是“Prepared transactions are disabled”,其正確的中文翻譯我覺(jué)得應(yīng)該是:預(yù)處理事務(wù)已被禁用。唉,所以我說(shuō)為什么要英文版,如果提示中文,想在網(wǎng)上找答案都會(huì)多些障礙。
對(duì)事務(wù)的使用,這里有個(gè)簡(jiǎn)單的例子:
using (NpgsqlConnection conn = new NpgsqlConnection(connectionStr)) {
conn.Open();
using (TransactionScope ts = new TransactionScope()) {
conn.EnlistTransaction(Transaction.Current);
//SQLs...
}
ts.Complete();
}
}
什么叫“預(yù)處理事務(wù)”?其實(shí)很簡(jiǎn)單,就是“事務(wù)包事務(wù)”,就是可以分步提交的事務(wù),比如我先開(kāi)啟了一個(gè)事務(wù)A,在這個(gè)事務(wù)中我又開(kāi)啟了一個(gè)事務(wù)B,B提交,A再提交。PG對(duì)于預(yù)處理事務(wù)是默認(rèn)關(guān)閉的,當(dāng)然了,你可以打開(kāi)它,編輯配置文件postgresql.conf,把max_prepared_transactions改為100(默認(rèn)是0,0表示禁用),重啟PG服務(wù)即可。
但你確定你真的用得到預(yù)處理事務(wù)嗎?我看下來(lái)我們是用不到的,但為什么出現(xiàn)這個(gè)問(wèn)題?——還是我們程序?qū)懙糜袉?wèn)題,即便你從單個(gè)方法上看不出來(lái)事務(wù)包事務(wù)。以下兩種場(chǎng)景可能會(huì)出現(xiàn)“預(yù)處理事務(wù)”:
1,我創(chuàng)建了一個(gè)方法A訪問(wèn)數(shù)據(jù)庫(kù),這個(gè)方法可能會(huì)被其它方法調(diào)用,所以它有個(gè)DbConnection類型的參數(shù),表示調(diào)用者負(fù)責(zé)打開(kāi)數(shù)據(jù)庫(kù)連接傳遞過(guò)來(lái),而A里面開(kāi)啟了事務(wù),而調(diào)用者并不知情,也開(kāi)啟了事務(wù),形成預(yù)處理事務(wù)
2,這種情況更隱晦些,數(shù)據(jù)庫(kù)連接字符串,如:Host=192.168.1.101; Username=postgres; Password=123456; Database=testdb; Enlist=true,在后面有個(gè)叫Enlist的參數(shù)為true,這表示這個(gè)連接在打開(kāi)的時(shí)候,會(huì)自動(dòng)Enlist到當(dāng)前執(zhí)行上下文的Transaction中去,如果當(dāng)前執(zhí)行上下文中打開(kāi)了事務(wù)(從代碼上看包含在了using(TransactionScope)中),那這個(gè)數(shù)據(jù)庫(kù)連接就自動(dòng)Enlist上去了,再考慮這樣的場(chǎng)景:A方法會(huì)自己打開(kāi)數(shù)據(jù)庫(kù)連接去查詢點(diǎn)什么東西,B方法也會(huì)訪問(wèn)數(shù)據(jù)庫(kù),且B方法會(huì)使用事務(wù),事務(wù)中調(diào)用了A方法,A方法打開(kāi)數(shù)據(jù)庫(kù)連接的時(shí)候發(fā)現(xiàn)當(dāng)前執(zhí)行上下文中存在Transaction,于是自動(dòng)Enlist上去了,不經(jīng)意間形成了預(yù)處理事務(wù),且還是“分布式”的(A和B打開(kāi)的可能是不同的數(shù)據(jù)庫(kù)連接),這種情況應(yīng)該并不是你所需要的
那我們應(yīng)該怎么做?下面是我的做法:
1,max_prepared_transactions還是設(shè)置為0,關(guān)掉,因?yàn)槲覀冋嬗貌坏剑绻玫玫?,那就是我們代碼寫錯(cuò)了,所以一旦出現(xiàn)“禁用已準(zhǔn)備好的事務(wù)”這個(gè)異常,就回去檢查代碼
2,把Enlist=true在數(shù)據(jù)庫(kù)連接字符串中去掉,這么一來(lái),每次使用事務(wù)都需要顯式地調(diào)用 conn.EnlistTransaction(Transaction.Current),雖然對(duì)了一行代碼,但語(yǔ)義更明確,也不用考慮到底是TransactionScope包DbConnection或反過(guò)來(lái)DbConnection包TransactionScope
3,規(guī)范化我們的數(shù)據(jù)庫(kù)訪問(wèn)代碼,明確哪些是需要事務(wù)哪些是不需要的,在各個(gè)方法的注釋上注明
40001:由于多個(gè)事務(wù)間的讀/寫依賴而無(wú)法串行訪問(wèn)
它對(duì)應(yīng)的英文是:Cound not serialize access due to read/write dependencies among transactions,這個(gè)應(yīng)該怎么理解呢?其實(shí)了解數(shù)據(jù)庫(kù)事務(wù)隔離級(jí)別的人對(duì)這個(gè)應(yīng)該不會(huì)陌生。.NET的TransactionScope默認(rèn)使用的是事務(wù)隔離級(jí)別中的最高級(jí)別——Serializable(可序列化)。這個(gè)級(jí)別最大程度上確保了數(shù)據(jù)的一致性,但代價(jià)也挺高,一來(lái)速度較慢,二來(lái)很容易出現(xiàn)“事務(wù)間讀/寫依賴”,就是這個(gè)錯(cuò)誤了,舉個(gè)簡(jiǎn)單的例子:
A、B兩個(gè)事務(wù),同時(shí)訪問(wèn)test表中id為50的一條記錄,A讀出這條記錄,接著B(niǎo)更新了這條記錄并提交,根據(jù)可序列化的隔離級(jí)別的規(guī)則,A并不知道B更新了記錄,A在B提交后嘗試修改這條記錄,這時(shí)候數(shù)據(jù)庫(kù)就會(huì)讓A事務(wù)失敗,并拋出這個(gè)異常,因?yàn)樽孉修改成功的話,就會(huì)導(dǎo)致B之前的修改不經(jīng)意間丟失了,可序列化隔離級(jí)別并不允許這種情況的發(fā)生。
所以,這是個(gè)“正常的錯(cuò)誤”,按常規(guī)的業(yè)務(wù)邏輯來(lái)說(shuō),應(yīng)該很少會(huì)出現(xiàn),如果真的出現(xiàn),且頻繁出現(xiàn),那需要考慮下是不是業(yè)務(wù)邏輯設(shè)計(jì)得不太合理,看看能不能從設(shè)計(jì)上避免這個(gè)問(wèn)題,如果業(yè)務(wù)邏輯一定如此,那可以用下面的方法嘗試一下:
1,將這種并行事務(wù)用客戶端代碼排個(gè)隊(duì),弄個(gè)線程安全隊(duì)列,逐個(gè)執(zhí)行,這樣速度會(huì)慢點(diǎn),但確保了每個(gè)事務(wù)都能成功
2,捕捉這個(gè)異常,然后自動(dòng)重試,其實(shí)這也是數(shù)據(jù)庫(kù)推薦的正統(tǒng)的做法
3,降低事務(wù)隔離級(jí)別,這個(gè)可能會(huì)出現(xiàn)問(wèn)題,也可能不出現(xiàn),這完全取決于你的業(yè)務(wù),關(guān)于事務(wù)隔離級(jí)別,這是個(gè)蠻大的話題,我考慮適當(dāng)時(shí)候再寫一篇文章
4,對(duì)于極少出現(xiàn)的頻次來(lái)說(shuō),可以不處理,僅僅需要捕捉這個(gè)異常類型,然后提示用戶重試即可,很多網(wǎng)站貌似都這么干的
總結(jié)
有時(shí)間的話我會(huì)另外開(kāi)一篇文章來(lái)寫寫PG的一些常規(guī)用法,如熱備冷備還原維護(hù)等,但不太能保證什么時(shí)候能寫出來(lái)。
到此這篇關(guān)于.NET+PostgreSQL實(shí)踐與避坑指南的文章就介紹到這了,更多相關(guān).NET+PostgreSQL實(shí)踐與避坑指南內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
來(lái)源:腳本之家
鏈接:https://www.jb51.net/article/205026.htm
申請(qǐng)創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!