若是應用程序遇到了下面錯誤信息,那麼意味着鏈接池(connection pool)的鏈接數量因爲一些緣由致使其超過了Max Pool Size參數的限制。sql
英文錯誤信息:數據庫
Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached設計模式
中文錯誤信息:服務器
超時時間已到。超時時間已到,可是還沒有從池中獲取鏈接。出現這種狀況多是由於全部池鏈接均在使用,而且達到了最大池大小。併發
在介紹這個錯誤前,咱們必須搞清楚一些概念,後續再展開分析這個問題出現的緣由,以及出現後如何解決問題。app
鏈接池(Connection Pool)asp.net
對於共享資源,有一個很著名的設計模式:資源池(resource pool)。該模式正是爲解決資源頻繁分配、釋放所形成的問題。數據庫鏈接池(connection pool)的基本思想就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只須要從緩衝池中取出一個鏈接,使用完畢後再放回去。數據庫鏈接池負責分配、管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是再從新創建一個。避免重複屢次的打開數據庫鏈接而形成的性能的降低問題和系統資源的浪費問題。less
鏈接池相關參數ide
因爲訪問數據庫的驅動不少,不一樣驅動的鏈接池參數等可能有所差別,具體以實際狀況爲準,咱們以ADO.NET爲例,來分析一個鏈接數據庫的鏈接配置,providerName="System.Data.SqlClient", 這裏沒有設置Max Pool Size、Pooing等參數,那麼其實都是取其對應的默認值。其中pooling參數默認狀況下爲true,Max Pool Size值爲100oop
<add name="xxxx" connectionString="Data Source = 192.168.xxx.xxx;Initial Catalog=xxx;User ID=xxxx;Password=xxxx;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient" />
Keyword |
Default |
Description |
Max Pool Size |
100 |
The maximum number of connections that are allowed in the pool. |
Min Pool Size |
0 |
The minimum number of connections that are allowed in the pool. |
Pooling |
'true' |
When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool. |
PoolBlockingPeriod |
Auto |
Sets the blocking period behavior for a connection pool. See PoolBlockingPeriod property for details. |
Keyword |
Default |
Description |
Max Pool Size |
100 |
池中容許的最大鏈接數。 |
Min Pool Size |
0 |
池中容許的最小鏈接數。 |
Pooling |
'true' |
若是此項的值設置爲 true,則在應用程序關閉時,將向池中添加任何新建立的鏈接。 在下一次嘗試打開相同的鏈接時,該鏈接將從池中提取。
若是鏈接具備相同的鏈接字符串,則將其視爲相同。 不一樣鏈接具備不一樣的鏈接字符串。
此鍵的值能夠爲 "true"、"false"、"yes" 或 "no"。 |
PoolBlockingPeriod |
Auto |
設置鏈接池的阻塞期行爲。 有關詳細信息,請參閱 PoolBlockingPeriod 屬性。 |
錯誤出現的緣由:
If we try to obtain connections more than max pool size, then ADO.NET waits for Connection Timeout for the connection from the pool. If even after that connection is not available, we get the following exception.
"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached"
The timeout expired. The timeout expired prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached
When you open an SQL Connection object, it always takes from an available pool of connections. When you close the connection, asp.net will release the connection to the pool of connections so that next connection object can use it.If you open connections with out closing them and when the pool reaches maximum connections, it will throw the specified error. Make sure you are not opening connection inside loop, if you open connection make sure you are closing it immedietly after you execute the query.
錯誤的緣由其實就是鏈接池中的鏈接被用完了,後面的請求創建數據庫鏈接時,鏈接池中沒有可用的資源(鏈接),那麼就分配到等待鏈接池分配的隊列中,而其等待的時間超過了參數「Connection Timeout」的值,就拋出這個異常信息。
錯誤的解決方案:
1:業務量忽然暴增,當前併發請求鏈接數據的數量超過了鏈接池的最大鏈接數(默認狀況下,最大鏈接數是100),出現這種狀況時,必須調整數據庫鏈接字符串參數Max Pool Size值爲一個合適的值。這種狀況通常較少見。
「Connection Pooling and the "Timeout expired" exception FAQ「中這篇文章中有代碼模擬這種狀況,這裏就不作展開了。有興趣的本身模擬一下便可。
SqlConnection[] connectionArray = new SqlConnection[101];
for (int i = 0; i <= 100; i++)
{
connectionArray[i] = new SqlConnection("Server=.\\SQLEXPRESS ;Integrated security=sspi;connection timeout=5");
connectionArray[i].Open();
}
2: leaking connections問題致使。
其實這裏我的理解爲數據庫鏈接沒有正常關閉。有些是代碼邏輯問題致使,有些是沒有正確處理異常問題。
案例1:
這個案例來自「Connection Pooling and the "Timeout expired" exception FAQ「中,它模擬的是一種邏輯異常問題。若是開發人員這樣寫代碼的話,那麼即便出現異常,可是數據庫鏈接永遠不會釋放(sqlconnection1.Close()永遠不會執行)
using System;
using System.Data;
using System.Data.SqlClient;
public class Repro
{
public static int Main(string[] args)
{
Repro repro = new Repro();
for (int i = 0; i <= 5000; i++)
{
try{ Console.Write(i+" "); repro.LeakConnections(); }
catch (SqlException){}
}
return 1;
}
public void LeakConnections()
{
SqlConnection sqlconnection1 = new SqlConnection("Server=.\\SQLEXPRESS ;Integrated security=sspi;connection timeout=5");
sqlconnection1.Open();
SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
sqlcommand1.CommandText = "raiserror ('This is a fake exception', 17,1)";
sqlcommand1.ExecuteNonQuery(); //this throws a SqlException every time it is called.
sqlconnection1.Close(); //We are calling connection close, and we are still leaking connections (see above comment for explanation)
}
}
解決方法:
咱們要保證每次調用鏈接的同時都在使用事後經過close()或dispose()對其執行了關閉.最簡單的辦法就是使用using,相似下面這樣的代碼
public void DoesNotLeakConnections()
{
Using (SqlConnection sqlconnection1 = new SqlConnection("Server=.\\SQLEXPRESS ;Integrated security=sspi;connection timeout=5")) {
sqlconnection1.Open();
SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
sqlcommand1.CommandText = "raiserror ('This is a fake exception', 17,1)";
sqlcommand1.ExecuteNonQuery(); //this throws a SqlException every time it is called.
sqlconnection1.Close(); //Still never gets called.
} // Here sqlconnection1.Dispose is _guaranteed_
}
案例2:
案例2來自官方文檔「Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool」,其實我也見過幾起相似這樣的案例。有現成的例子,就不必本身構造這樣的案例。
static void Main(string[] args)
{
string connString = @"Data Source=<your server>;Initial Catalog=Northwind;Integrated Security=True; Max Pool Size=20; Connection Timeout=10";
try
{
for (int i = 0; i < 50; i++)
{
// Create connection, command and open the connection
SqlConnection sc = new SqlConnection(connString);
SqlCommand sCmd = new SqlCommand("SELECT * FROM Shippers", sc);
sc.Open();
// Print info
Console.WriteLine("Connections open: {0}", i.ToString());
// This will cause the error to show.
SqlDataReader sdr = sCmd.ExecuteReader();
sdr.Close();
// Replacing the two rows above with these will remove the error
//SqlDataReader sdr = sCmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
//sdr.Close();
// -- or --
// Explicity close the connection
//sc.Close();
}
// -- or --
// Run all in a Using statement (in this case, replace the whole for loop with the loop below.).
//for (int i = 0; i < 50; i++)
//{
// using (SqlConnection sc = new SqlConnection(connString))
// {
// SqlCommand sCmd = new SqlCommand("SELECT * FROM Shippers", sc);
// sc.Open();
// Console.WriteLine("Conns opened " + i.ToString());
// SqlDataReader sdr = sCmd.ExecuteReader();
// sdr.Close();
// }
//}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
3:經過visual Studio中的sql debugging 來打開或關閉鏈接的, 這個參考「Connection Pooling and the "Timeout expired" exception FAQ中」的詳細介紹。我的卻是沒有遇到過這種狀況。
我的的一些經驗體會,遇到這些問題後,咱們首先應該監控數據庫的鏈接數量信息,相似下面這樣的SQL,根據實際狀況來調整(例如,有些程序的IIS部署在某個應用服務器上,咱們經過hostname基本上就能定位)
SELECT hostname ,
COUNT(*) AS connection_sum
FROM sys.sysprocesses
GROUP BY hostname
ORDER BY 2 DESC;
SELECT loginame ,
COUNT(*) AS connection_sum
FROM sys.sysprocesses
GROUP BY loginame
ORDER BY 2 DESC;
而後要跟開發人員協做檢查鏈接數據庫的配置信息(鏈接字符串設置),例如Max Pool Size的大小設置,而後就是最麻煩的問題,怎麼定位到root cause呢? 這就須要開發人員去檢查了。已經脫離了DBA的掌控了。我的經驗(不作開發多年了,經驗都過期了),若是忽然出現這個問題,而且有源代碼版本控制管理,最好找出最近的修改部分,進行細緻的檢查驗證,基本上就能定位到問題根源了。可是可否作到細緻檢查,因人而異。這個每每最難掌控,跟我的的態度、經驗、能力有很大關係。
參考資料:
https://blogs.msdn.microsoft.com/spike/2008/08/25/timeout-expired-the-timeout-period-elapsed-prior-to-obtaining-a-connection-from-the-pool/