- 背景:
在最近開發中遇到一個問題,對一個數據庫進行操作時,我采用64個並行的任務每個任務保證一個數據庫連接對象;但是每個任務內部均包含有24個文件需要讀取,在讀取文件之后,我們需要快速將這24個文件批量入庫到數據庫中。
於是我這樣開發我的程序:
主任務處理方式:最多允許64並行主任務;
主任務內部子任務采用串行方式:24個文件依次讀取,和當前主任務均使用同一個數據庫連接字符串。
每個主任務都需要24個文件入庫到各自的物理分表中,采用的是串行讀取文件資源,串行入庫,沒有能並行插入24個批處理文件到當前主任務的分表中,感覺到這可能是數據庫IO入庫速度不高是一個主要因素。
於是包主任務中的24個文件解析入庫子任務改為並行方式,結果問題來了:
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.
- 排查問題:
1、查看當前數據庫已經占用的連接數方法:
A、通過perfiler監控我當前的連接數多少?采用Standard(default)監控模式,stop-》start,每次開始時,就會快速羅列出當前已經建立的連接列表,從列表記錄中可以查看到目前數據庫實例被占用的數據庫連接數。
B、從sys.dm_os_performance_counters中查找
declare @value int; while 1=1 begin waitfor delay '00:00:01' select @value=cntr_value from sys.dm_os_performance_counters where counter_name ='User Connections' print @value; end
從上邊的監控中查詢出,目前我們的數據庫連接數確實就只有100個左右,怎么就出現連接池滿的問題。前50個是系統連接占用,不計算,那么,我們可以使用的連接數只有將近50個就出現連接池滿的異常了。
吃驚!
我們查看下數據庫連接數設置是否有問題,通過界面查看:
貌似一切正常,沒有限制,難道100個連接就達到最大連接數了?!!!
我們執行查詢最大連接數查詢sql命令:
select * from sys.configurations where name ='user connections'
sqlserver難道在欺騙我們,絕不可能,那么大個公司,如果不能處理這么多,就不會不負責人多高數用戶最多允許32767個連接。
我們注意到,這里的value_in_use字段和從數據庫界面上看到的一樣,都是0,沒有限制,那么就是說我們默認就是不受限制,可以最多使用32767個連接。
- 問題發現:
那么,是誰給我們制定了最大連接數限制為100呢?
記得在數據庫連接字符串中是包含這個設置的Max Pool Size屬性,趕快查查MSDN,確認下是不是這里不寫的話有默認值。
搜索關鍵字“SqlConnection.ConnectionString 屬性”,找打了MSDN:https://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqlconnection.connectionstring(VS.80).aspx
名稱 |
默認值 |
說明 |
---|---|---|
Connection Lifetime |
0 |
當連接被返回到池時,將其創建時間與當前時間作比較,如果時間長度(以秒為單位)超出了由 Connection Lifetime 指定的值,該連接就會被銷毀。這在聚集配置中很有用(用於強制執行運行中的服務器和剛置於聯機狀態的服務器之間的負載平衡)。 零 (0) 值將使池連接具有最大的連接超時。 |
Connection Reset |
'true' |
確定從池中提取數據庫連接時是否重置數據庫連接。對於 SQL Server 7.0 版,設置為 false 可避免獲取連接時再有一次額外的服務器往返行程,但須注意此時並未重置連接狀態(如數據庫上下文)。 只要不將 Connection Reset 設置為 false,連接池程序就不會受到 ChangeDatabase 方法的影響。連接在退出相應的連接池以后將被重置,並且服務器將移回登錄時數據庫。不會創建新的連接,也不會重新進行身份驗證。如果將 Connection Reset 設置為 false,則池中可能會產生不同數據庫的連接。 |
Enlist |
'true' |
當該值為 true 時,池程序在創建線程的當前事務上下文中自動登記連接。可識別的值為 true、false、yes 和no。 |
Load Balance Timeout |
0 |
連接被銷毀前在連接池中生存的最短時間(以秒為單位)。 |
Max Pool Size |
100 |
池中允許的最大連接數。 |
Min Pool Size |
0 |
池中允許的最小連接數。 |
Pooling |
'true' |
當該值為 true 時,系統將從適當的池中提取 SQLConnection 對象,或在需要時創建該對象並將其添加到適當的池中。可識別的值為 true、false、yes 和 no。 |
當設置需要布爾值的關鍵字或連接池值時,您可以使用“yes”代替“true”,用“no”代替“false”。整數值表示為字符串。
Max Pool Size 默認為100。
- 問題驗證:
問題終於鎖定了,那么是否真的是這樣子呢?
我做了一下測試:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ThreadPool.SetMaxThreads(1000, 1000); 6 7 for (var i = 0; i < 1000; i++) 8 { 9 ThreadPool.QueueUserWorkItem(Connection); 10 } 11 12 while (true) 13 { 14 Thread.Sleep(1000); 15 Console.WriteLine("alive..."); 16 } 17 18 Console.WriteLine("Complete!!!"); 19 Console.ReadKey(); 20 } 21 22 private static void Connection(object state) 23 { 24 SqlConnection connection = new SqlConnection("Data Source=.;Initial Catalog=i_Master;Persist Security Info=True;User ID=NUser;Password=N2;max pool size=500"); 25 26 connection.Open(); 27 28 using (SqlCommand command = new SqlCommand()) 29 { 30 command.Connection = connection; 31 command.CommandTimeout = 30 * 60; 32 command.CommandText = @" 33 SELECT @@VERSION; 34 waitfor delay '00:00:01' 35 "; 36 command.CommandType = System.Data.CommandType.Text; 37 38 command.ExecuteNonQuery(); 39 } 40 41 42 Thread.Sleep(10000); 43 } 44 }
我設定1000個線程,在.net線程池中運行,每個線程打開數據庫連接字符串后不關閉,讓他永久占用連接,直到對象被回收或者連接超時。
通過上邊兩種監控連接的方式,查看到的結果:
A方式監控結果:
B方式監控結果:
- 參考資料:
Configure the user connections Server Configuration Option:https://technet.microsoft.com/en-us/library/ms187030.aspx
Connection Pooling and the “Timeout expired” exception FAQ:https://blogs.msdn.microsoft.com/angelsb/2004/08/25/connection-pooling-and-the-timeout-expired-exception-faq/
Max Connection Pool capped at 100:http://dba.stackexchange.com/questions/51219/max-connection-pool-capped-at-100
SqlConnection.ConnectionString 屬性:https://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqlconnection.connectionstring(VS.80).aspx