數據庫連接池

連  接  池

與微軟以前的數據訪問技術類似,ADO.NET包括對連接池的內置支持。

1  連接句柄和物理連接

如果正在使用Visual Studio,可以使用Visual Studio調試工具檢查對象的一些內部私有屬性。例如,編寫一些代碼來打開一個SqlConnection,並在調用Open方法的地方設置斷點。右擊代碼中的對象,並選擇【添加監視】,將該對象添加到【監視】窗口。在【監視】窗口中,展開標有Non-Pubic Members的區域。向下滾動,將會看到一個稱爲InnerConnection的私有屬性。

從結構上講,InnerConnection屬性的內容是一個非常薄的層,位於數據庫的物理連接之上。爲在這裏進行討論,InnerConnection屬性和到該數據庫的物理連接是可交換的。在逐步執行代碼時,將會看到在打開和關閉連接時,InnerConnection屬性的值發生變化。當調用Open方法時,SQL Client .NET數據提供程序將SqlConnection對象關聯至該數據庫的物理連接,所以可以執行查詢並返回結果。

打開和關閉數據庫連接的代價非常高。爲了幫助節省資源並提高性能,.NET Framework中的.NET數據提供程序在默認情況下均使用連接池。

2  連接池是什麼

連接池是一種在打開數據存儲區的連接時提高應用程序性能的機制。在調用SqlConnection對象的Close方法時,SQL Client .NET數據提供程序並不實際關閉內部連接。相反,數據提供程序將該內部連接存儲到一個池中,以便在以後再次使用。甚至在SqlConnection對象被處理之後,該內部連接也保留在池中。如果在以後使用相同連接字符串和憑據調用SqlConnection對象的Open方法,將會再次使用同一內部連接與數據庫進行通信。

如果希望確認是否真正再次利用了同一內部連接,可以使用.NET Reflection中的功能以可編程方式訪問私有InnerConnection屬性的內容。以下代碼(其需要對System.Reflection命名空間的引用)在Using代碼塊中打開一個SqlConnection,並存儲SqlConnection的InnerConnection屬性的值。通過利用Using代碼塊,在該代碼塊的末尾隱式處理了SqlConnection。此代碼在Using代碼塊中打開另一個SqlConnection,並存儲SqlConnection的InnerConnection屬性的值。最後,此代碼對比InnerConnection屬性的內容,確認它們實際上爲同一對象。

Visual Basic

Dim strConn As String = "Data Source=./SQLExpress;Integrated Security=True;"
Dim propInnerConn As PropertyInfo
propInnerConn = GetType(SqlConnection).GetProperty("InnerConnection", _
                            BindingFlags.NonPublic or BindingFlags.Instance)
Dim objInnerConn1, objInnerConn2 As Object
Using cn As New SqlConnection(strConn)
    cn.Open()
    objInnerConn1 = propInnerConn.GetValue(cn, Nothing)
    cn.Close()
End Using

Using cn As New SqlConnection(strConn)
    cn.Open()
    objInnerConn2 = propInnerConn.GetValue(cn, Nothing)
    cn.Close()
End Using

Console.WriteLine(objInnerConn1 Is objInnerConn2)

Visual C#

string strConn = @"Data Source=./SQLExpress;Integrated Security=True;";
PropertyInfo propInnerConn;
propInnerConn = typeof(SqlConnection).GetProperty("InnerConnection",
                                BindingFlags.NonPublic | BindingFlags.Instance);
object objInnerConn1, objInnerConn2;
using (SqlConnection cn = new SqlConnection(strConn))
{
    cn.Open();
    objInnerConn1 = propInnerConn.GetValue(cn, null);
    cn.Close();
}

using (SqlConnection cn = new SqlConnection(strConn))
{
    cn.Open();
    objInnerConn2 = propInnerConn.GetValue(cn, null);
    cn.Close();
}

Console.WriteLine(objInnerConn1 == objInnerConn2);

兩個SqlConnection對象是在不同的Using代碼塊中創建的,所以其資源將在每個Using代碼塊的末尾被清除。InnerConnection屬性的內容及其所封裝的物理連接沒有被處理,而是存儲在池中,以便在以後被再次利用。

 注意    如果在連接字符串中禁用了連接池(稍後將解釋如何禁用),將會看到此內部連接不能被重複利用。

3  連接池如何改進代碼

考慮一個訪問SQL Server數據庫的典型ASP.NET或WebServices應用程序。客戶端應用程序每次需要查詢數據庫時,就會在服務器端代碼中進行往返,以打開SqlConnection來執行查詢。在許多此類應用程序中,這一代碼以相同憑據一次又一次地連接到相同數據庫。理論上,這意味着客戶端應用程序每次需要執行查詢時,服務器端代碼需要執行三個操 作——登錄到數據庫(需要檢查所提供的憑據)、執行查詢、然後註銷。

連接池可以真正地提高此類應用程序的性能。通過將內部連接存儲在池中,並在以後進行重複利用,就不再因爲登錄數據庫以及從中註銷而降低性能。對SqlConnection對象的Open和Close方法的調用可以短時間內返回,從而可以提高代碼的性能和響應速度(請參見圖3.4)。

圖3.4  典型ASP.NET或WebServices應用程序中的連接池

4  啓用連接池

在ADO.NET中,連接池是默認啓用的。以下代碼段將同一SqlConnection對象打開和關閉5次。由於連接池是默認啓用的,所以當調用Close方法時,到數據庫的實際連接沒有被實際關閉,而是將該數據庫連接發送到池中,以便在以後重複利用。

Visual Basic

Dim strConn As String
strConn = "Data Source=./SQLExpress;Integrated Security=True;"
Dim cn As New SqlConnection(strConn)
For intCounter As Integer = 1 To 5
    cn.Open()
    cn.Close()
Next intCounter

Visual C#

string strConn;
strConn = @"Data Source=./SQLExpress;Integrated Security=True;";
SqlConnection cn = new SqlConnection(strConn);
for (int intCounter = 1; intCounter <= 5; intCounter++)

    cn.Open();
    cn.Close();
}

5  放入池中的連接何時關閉

在調用Close方法時,SqlClient將該連接返回到池中。假定該連接沒有被再次使用,將在大約5分鐘後將其從池中刪除。但具體在多少秒後刪除,並沒有確切的數值。其行爲取決於所生成的隨機數以及創建該池時的相對溼度(relative humidity)。當然,如果在退出應用程序時存在已打開的連接池,那麼作爲應用程序正常清除過程的一部分,這些連接將被關閉和處理。

6  禁用連接池

您可能不希望使用連接池。例如,如果正在使用一個直接與數據庫進行通信的簡單Windows應用程序,那麼可能希望禁用連接池。在採用這一架構時,各個客戶端應用程序需要自己的連接。在啓用連接池時,每個應用程序的連接被放入池中,如果在清除連接池之前重新打開該連接,將重複利用放入池中的連接。所以,如果應用程序頻繁重複使用連接,那麼在啓用連接池的情況下,對SqlConnection.Open的調用將會更快速地返回。但是,這種方法將會導致在任意給定時刻存在許多活動的數據庫連接。禁用連接池將會降低任意時刻的活動數據庫連接數目,但這樣會強制所有對SqlConnection.Open的調用都建立一個新的數據庫連接。

如果希望禁用連接池,可以通過向連接字符串中添加Pooling=False,逐個連接地禁用連接池。

幸運的是,在ADO.NET 2.0中不再需要記憶諸如此類的屬性。如果存在疑問,可以檢查SqlConnectionStringBuilder類的選項。在這個類中可以找到一個Pooling屬性,其取值爲Boolean類型。默認情況下,此值被設置爲True。將該值設置爲False將會禁止將該連接放入池中。因此,在調用SqlConnection對象的Close方法時,將會關閉與數據庫的實際連接。

 注意    在“偶爾進行連接”的Windows應用程序中,使用連接池可能很有幫助,具體取決於應用程序。如果應用程序希望定期重新連接到數據庫,則可以發揮連接池的作用,將與數據庫的物理連接保持打開狀態,至少暫時如此。如果在從池中刪除該物理連接之前,應用程序嘗試重新連接到該數據庫,則連接池邏輯(pooling logic)將會重新使用與該數據庫的物理連接。

7  有關連接池的常見問題

學習連接池的開發人員越多,出現的問題也會越多。例如,在我聽到的連接池相關問題中,最常見的一個是“我怎樣才能知道與數據庫的物理連接是被真正關閉了,還是僅僅被放入池中了?”,另一個常見問題是“我怎樣才能分辨剛剛打開的連接是建立了一個新物理連接,還是重新利用了一個被放在池中的連接?”。

有許多工具可以幫助回答有關連接池的問題。其中一些工具更出色一些。我定期使用SQL Server事件探查器來監視對SQL Server數據庫的連接和查詢。在ADO.NET的2.0版中,還可以使用Windows中的【性能監視器】。

ADO.NET 2.0中的SQL Client .NET數據提供程序包括用於連接池的性能計數器。現在可以使用諸如【性能監視器】等工具來查看以下數目:放入池中的連接、活動連接、自由連接、活動與非活動連接池及活動與非活動連接池組。還可以蒐集有關在每秒內進行連接和斷開連接數目的信息。

在某些情況下,維護性能計數器會產生一些性能影響。爲此,SQL Client .NET數據提供程序沒有維護以下性能計數器:活動或自由連接的數目,或者每秒內放入池中的連接數目或斷開連接的數目。可以通過嚮應用程序的配置文件中添加一項,以啓用應用程序中的這些性能計數器。如需有關使用這些性能計數器的詳細信息,請參閱MSDN網站上的文章“Using ADO.NET Performance Counters”(使用ADO.NET性能計數器)。

爲便於您提出有關連接池的問題,也便於我回答這些問題,我已經開發了一個示例應用程序,如圖3.5所示,它可以作爲本書示例代碼中的一部分進行下載。這一應用程序允許使用如圖3.3所示的SqlConnectionStringBuilder/PropertyGrid對話框生成連接字符串。可以很容易地生成新的SqlConnection,打開和關閉現有連接,以及調用ClearPool和ClearAllPools方法。此示例還可以通過【性能監視器】訪問SQL Client性能計數器,而不需要以手動方式添加性能計數器。此應用程序的配置文件中包含一項,其能夠啓用在默認情況下被關閉的性能計數器。在每次創建、打開或關閉SqlConnection或關閉一個或所有連接池時,此示例中的性能計數器都會被更新。

圖3.5  研究連接池

8  ADO.NET如何確定是否使用放入池中的連接

簡單地說,假定連接池未被禁用,則SQL Client .NET數據提供程序在您調用SqlConnection對象的Open方法時檢查ConnectionString,並確定池中是否存在可用連接。如果存在可用連接,則SQL Client使用該連接。否則,打開一個到數據庫的新連接。

實際上還有一些需要說明的內容。設想一個ASP.NET應用程序,其中有多位用戶以模擬(impersonation)登錄同一數據庫,每位用戶都使用自己的憑據訪問SQL Server數據庫。每位用戶的連接字符串都是相同的,但他們的憑據有很大不同。由於SQL Client考慮了用戶權限,所以用於確定池中有哪些連接可供使用的邏輯要稍微複雜一些。

9  強制ADO.NET使用新池

在某些時候,可能不再希望從舊連接池中提取連接,而希望建立一個新池。在這種情況下,目標就是採用某種能夠對池緩存(pooling)產生影響的方式來修改連接字符串,而對應用程序的剩餘部分不產生影響。達到這一目標的最簡單方式是在連接字符串的末尾添加一個空格。

10  手動釋放池中緩存的連接

前面的技巧對於ADO.NET 1.x版非常方便,因爲API的特性都不能幫助釋放緩存在池中的連接。在ADO.NET 2.0中,SqlConnection類中有兩個新的靜態方法可提供幫助——ClearPool和ClearAllPools。

ClearPool方法取得一個SqlConnection對象,並釋放緩存在一個連接池中的所有連接,此連接池與該SqlConnection對象相關。假定有10個SqlConnection對象,均使用同一連接字符串和憑據,並啓用了連接池。可以調用所有10個對象的Open方法,然後調用其中三個對象的Close方法。

共有10個到SQL Server數據庫的開放式連接。這些連接中的7個連接與7個打開的SqlConnection對象相關聯。爲了使用性能計數器所使用的術語,我們說這7個連接是“活動的”。剩下的三個連接存在於連接池中。再一次使用性能計數器的術語,它們是“自由”連接。調用ClearPool方法將釋放這三個存在於連接池中的自由連接,但不會影響由7個開放式SqlConnection對象所使用的活動連接。

ClearAllPools方法沒有參數,它會清除所有自由SqlConnection。

11  其他連接池選項

現在簡單看一下其他常用連接池選項。每個選項都可通過SqlConnectionStringBuilder和連接字符串使用。

1. Connection Reset

如果仔細查看一個SQL事件探查器的軌跡,可能會注意到調用了一個名爲sp_reset_connection的存儲過程,您可能希望知道是何時調用這個存儲過程的,以及爲什麼要調用它。

簡單地重複使用緩存在池中的SqlConnection可能會產生意料之外的結果。由於缺乏更合適的術語,所以可以說:存在某個與已緩存(pooled)連接相關聯的“殘餘”。開發人員不會總是在關閉連接時清除這些混亂,以將連接返回到其原始狀態。在調用Close方法時,可能已經存在與該連接相關聯的開放式遊標或事務,甚至更糟。如果發出一個“USE AdventureWorks”之類的查詢,該連接可能被關聯到一個不同於該數據庫被打開時的數據庫。如果通過調用sq_setapprol爲連接指定應用程序角色,這些權限仍然被應用到該連接,直到sp_unsetapprole被調用爲止,或者直到sp_reset_connection被調用爲止,或者直到該連接被真正關閉(而不僅僅是被放入池中)爲止。

SQL Client .NET數據提供程序跟蹤哪些SqlConnection使用了從連接池中提取的連接。在調用Open方法時,SqlClient沒有調用sp_reset_connection存儲過程,而是恰好在該連接的第一個操作之前執行這一查詢。

如果能夠絕對肯定沒有在服務器上爲池中緩存的連接留下任何“殘餘”,就可以向連接字符串中添加Connection Reset=False;。這一設置告訴SqlClient,當重新利用緩存在池中的連接時,不需要調用sp_reset_connection存儲過程。但是,我建議不要將Connection Reset設置爲False。

2. Min Pool Size

從其名稱可以看出,Min Pool Size控制池中的最少連接數目。默認情況下,此屬性被設置爲0。

Min Pool Size屬性可以幫助您準備一個連接池。假定將屬性設置爲5。在打開第一個連接之後,SqlClient將在一後臺線程中打開另外4個連接。在該池中總是至少有5個連接。假定代碼創建了10個SqlConnection對象,並將它們全部打開。和以前一樣,在打開第一個SqlConnection時,SqlClient將在後臺線程打開其他4個連接。接下來的4個SqlConnection對象將使用來自池中的剩餘連接。剩餘的5個SqlConnection對象將建立新連接。

現在,假定關閉了這10個SqlConnection對象中的8個對象。所有這8個連接都將在池中保持存活。當SqlClient清除此連接池之後(假定大約在這些SqlConnection被關閉5分鐘之後,並且沒有被重新使用),它將確保至少有5個連接(Min Pool Size設置)仍然存在於連接池中。此數目包括目前使用的連接,兩種連接仍在使用。所以將有三個其他連接保存在連接池中。其餘5個連接將被丟棄。

Min Pool Size的主要缺點是池中將至少有設定數目的連接保持活動。在ASP.NET應用程序中,這些連接的存在時間可能過長。因此,最好使Min Pool Size保持爲零。

3. Max Pool Size

Max Pool Size設置的理解稍微簡單一些。此設置用作一種限制特性,防止在單個池中打開的連接數目超出指定數值。Max Pool Size的默認值爲100。如果達到了連接池中的最大連接數目,那麼在下一次嘗試打開一個連接時,經過Connect Timerout設置所指定的時間之後會引發一個InvalidOperationException,其說明“超時時間已到。在從池中獲取連接之前超時時間已過。出現這種情況可能是因爲所有池連接都已被使用,並已達到最大池 大小”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章