但是尚未从池中获取连接_SQLServer超时时间已到,但是尚未从池中获取连接
小編最近開發(fā)了一個(gè)項(xiàng)目,數(shù)據(jù)庫(kù)是SQLServer2008R2,在WinForm程序通過(guò)API接口短時(shí)間大批量上傳數(shù)據(jù)時(shí),出現(xiàn)了錯(cuò)誤”超時(shí)時(shí)間已到,但是尚未從池中獲取連接”,數(shù)據(jù)是一包一包上傳的,每次調(diào)用都對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,全部無(wú)誤之后才會(huì)插入數(shù)據(jù)庫(kù)。從網(wǎng)上得到的結(jié)果是所有池連接均在使用,并且達(dá)到了最大池大小,在connectionString中如果未指定max pool size的值,則max pool size=100,當(dāng)訪問(wèn)人員同時(shí)連接數(shù)據(jù)庫(kù)的數(shù)量為101人時(shí),則等待SqlConnection.ConnectionTimeout設(shè)置的時(shí)間(默認(rèn)是15 秒)后,還是沒(méi)有可用的Connection則會(huì)出現(xiàn)上面的錯(cuò)誤。
Connection Pool 是什么呢 ?
每當(dāng)程序需要讀寫數(shù)據(jù)庫(kù)的時(shí)候。Connection.Open()會(huì)使用ConnectionString連接到數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)會(huì)為程序建立 一個(gè)連接,并且保持打開狀態(tài),此后程序就可以使用T-SQL語(yǔ)句來(lái)查詢/更新數(shù)據(jù)庫(kù)。當(dāng)執(zhí)行到Connection.Close()后,數(shù)據(jù)庫(kù)就會(huì)關(guān)閉當(dāng) 前的連接。很好,一切看上去都是如此有條不紊。但是如果我的程序需要不定時(shí)的打開和關(guān)閉連接,(比如說(shuō) ASP.Net 或是 Web Service ),例如當(dāng)Http Request發(fā)送到服務(wù)器的時(shí)候,我們需要打開Connection 然后使用Select* from Table 返回一個(gè)DataTable/DataSet給客戶端/瀏覽器,然后關(guān)閉當(dāng)前的Connection。那每次都Open/Close Connection 如此的頻繁操作對(duì)于整個(gè)系統(tǒng)無(wú)疑就成了一種浪費(fèi)。
ADO.Net Team就給出了一個(gè)比較好地解決方法。將先前的Connection保存起來(lái),當(dāng)下一次需要打開連接的時(shí)候就將先前的Connection 交給下一個(gè)連接。這就是Connection Pool。
Connection Pool 如何工作的?
首先當(dāng)一個(gè)程序執(zhí)行Connection.open()時(shí)候,ADO.net就需要判斷,此連接是否支持Connection Pool (Pooling 默認(rèn)為True),如果指定為False, ADO.net就與數(shù)據(jù)庫(kù)之間創(chuàng)建一個(gè)連接(為了避免混淆,所有數(shù)據(jù)庫(kù)中的連接,都使用”連接”描述),然后返回給程序。 如果指定為 True,ADO.net就會(huì)根據(jù)ConnectString創(chuàng)建一個(gè)Connection Pool,然后向Connection Pool中填充Connection(所有.net程序中的連接,都使用”Connection”描述)。填充多少個(gè)Connection由Min Pool Size (默認(rèn)為0)屬性來(lái)決定。例如如果指定為5,則ADO.net會(huì)一次與SQL數(shù)據(jù)庫(kù)之間打開5個(gè)連接,然后將4個(gè)Connection,保存在 Connection Pool中,1個(gè)Connection返回給程序。
當(dāng)程序執(zhí)行到Connection.close() 的時(shí)候。如果Pooling 為True,ADO.net 就把當(dāng)前的Connection放到Connection Pool并且保持與數(shù)據(jù)庫(kù)之間的連接。 同時(shí)還會(huì)判斷Connection Lifetime(默認(rèn)為0)屬性,0代表無(wú)限大,如果Connection存在的時(shí)間超過(guò)了Connection LifeTime,ADO.net就會(huì)關(guān)閉的Connection同時(shí)斷開與數(shù)據(jù)庫(kù)的連接,而不是重新保存到Connection Pool中。 (這個(gè)設(shè)置主要用于群集的SQL 數(shù)據(jù)庫(kù)中,達(dá)到負(fù)載平衡的目的)。如果Pooling指定為False,則直接斷開與數(shù)據(jù)庫(kù)之間的連接。
然后當(dāng)下一次Connection.Open() 執(zhí)行的時(shí)候,ADO.Net就會(huì)判斷新的ConnectionString與之前保存在Connection Pool中的Connection的connectionString是否一致。ADO.Net會(huì)將ConnectionString轉(zhuǎn)成二進(jìn)制流,所以也就是說(shuō),新的ConnectionString與保存在Connection Pool中的Connection的ConnectionString必須完全一致,即使多加了一個(gè)空格,或是修改了Connection String中某些屬性的次序都會(huì)讓ADO.Net認(rèn)為這是一個(gè)新的連接,而重新創(chuàng)建一個(gè)新的連接。所以如果您使用的UserID,Password的認(rèn) 證方式,修改了Password也會(huì)導(dǎo)致一個(gè)Connection,如果使用的是SQL的集成認(rèn)證,就需要保存兩個(gè)連接使用的是同一個(gè))。 然后 ADO.net需要判斷當(dāng)前的Connection Pool中是否有可以使用的Connection(沒(méi)有被其他程序所占用),如果沒(méi)有的話,ADO.net就需要判斷ConnectionString設(shè) 置的Max Pool Size (默認(rèn)為100),如果Connection Pool中的所有Connection沒(méi)有達(dá)到Max Pool Size,ADO.net則會(huì)再次連接數(shù)據(jù)庫(kù),創(chuàng)建一個(gè)連接,然后將Connection返回給程序。 如果已經(jīng)達(dá)到了 MaxPoolSize,ADO.net就不會(huì)再次創(chuàng)建任何新的連接,而是等待Connection Pool中被其他程序所占用的Connection釋放,這個(gè)等待時(shí)間受SqlConnection.ConnectionTimeout(默認(rèn)是15 秒)限制,也就是說(shuō)如果時(shí)間超過(guò)了15秒,SqlConnection就會(huì)拋出超時(shí)錯(cuò)誤(所以有時(shí)候如果SqlConnection.open()方法拋 出超時(shí)錯(cuò)誤,一個(gè)可能的原因就是沒(méi)有及時(shí)將之前的Connnection關(guān)閉,同時(shí)Connection Pool數(shù)量達(dá)到了MaxPoolSize。) 如果有可用的Connection,從Connection Pool 取出的Connection也不是直接就返回給程序,ADO.net還需要檢查ConnectionString的ConnectionReset屬性 (默認(rèn)為True)是否需要對(duì)Connection 最一次reset。這是由于,之前從程序中返回的Connection可能已經(jīng)被修改過(guò),比如說(shuō)使用 SqlConnection.ChangeDatabase method 修改當(dāng)前的連接,此時(shí)返回的Connection可能就已經(jīng)不是連接當(dāng)前的Connection String指定的Initial Catalog數(shù)據(jù)庫(kù)了,所以需要reset一次當(dāng)前的連接。
解決方法
1. 修改幾個(gè)關(guān)鍵頁(yè)面或訪問(wèn)比較頻繁的數(shù)據(jù)庫(kù)訪問(wèn)操作,使用DataAdapter和DataSet來(lái)獲取數(shù)據(jù)庫(kù)數(shù)據(jù),不要使用DataReader。
DataReader是獨(dú)占連接的,每個(gè)DataReader都要占用一個(gè)連接。當(dāng)然這個(gè)情況是偶爾出現(xiàn)的,所以會(huì)很長(zhǎng)時(shí)間出現(xiàn)一次,因?yàn)橹挥型瑫r(shí)有超過(guò)連接池最大連接數(shù)量的并發(fā)操作才會(huì)發(fā)生。比如說(shuō)最大連接設(shè)100,假設(shè)有100個(gè)人同時(shí)使用DataReader正在讀取數(shù)據(jù)庫(kù)內(nèi)容,那么當(dāng)?shù)?01人讀取的時(shí)候,連接池中的連接已經(jīng)沒(méi)有了,就會(huì)出現(xiàn)上面的錯(cuò)誤。而且你加大并發(fā)數(shù)量只能暫時(shí)緩解問(wèn)題,如果你加大到200個(gè)并發(fā)連接,如果有201人同時(shí)操作怎么辦?你說(shuō)了你使用Connection對(duì)象的Close()方法,這是不行的,因?yàn)镃lose()方法僅僅是關(guān)閉連接,但這個(gè)連接沒(méi)有釋放,還是被這個(gè)對(duì)象占用,要釋放必須使用Connection的Dispose()方法顯式釋放連接才可以,否則這個(gè)對(duì)象占用的連接只能等到垃圾收集的情況下才能被釋放。這種情況肯定會(huì)出現(xiàn)“超時(shí)時(shí)間已到”的錯(cuò)誤。
2. 在訪問(wèn)數(shù)據(jù)庫(kù)的頁(yè)面上使用數(shù)據(jù)緩存,如果頁(yè)面的數(shù)據(jù)不是經(jīng)常更新(幾分鐘更新一次)的話,使用Cache對(duì)象可以不用訪問(wèn)數(shù)據(jù)庫(kù)而使用緩存中的內(nèi)容,那么可以大大減少連接數(shù)量。
3. 修改代碼,把使用Connection對(duì)象的地方都在Close()后面加上Dispose()調(diào)用。
4. 建議對(duì)數(shù)據(jù)庫(kù)操作進(jìn)行大的修改,建立自己的數(shù)據(jù)庫(kù)操作代理類,繼承System.IDisposable接口,強(qiáng)迫釋放資源,這樣就不會(huì)出現(xiàn)連接數(shù)量不夠的問(wèn)題了。
總結(jié)
以上是生活随笔為你收集整理的但是尚未从池中获取连接_SQLServer超时时间已到,但是尚未从池中获取连接的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 为什么冲马桶前,一定要先盖盖子?
- 下一篇: 复杂查询练习_《从零学会SQL:简单查询