連接池和 "Timeout expired"異常【轉】


異常信息:

MySql.Data.MySqlClient.MySqlException (0x80004005): error connecting: 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.
at MySql.Data.MySqlClient.MySqlPool.GetConnection()
at MySql.Data.MySqlClient.MySqlConnection.Open()
at ArticleSys.MySqlHelper.PrepareCommand(MySqlCommand cmd, MySqlConnection conn, MySqlTransaction trans, CommandType cmdType, String cmdText, MySqlParameter[] cmdParms)
at ArticleSys.MySqlHelper.GetDataTable(String mysqlConnStr, CommandType cmdType, String cmdText, MySqlParameter[] commandParameters)
=====================2013-10-10 12:28:33==============

------------以下為正文

System.InvalidOperationException: 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.


Timeout expired 異常是個很棘手的異常,想必幾乎每個人都碰到過。有時可真是對它咬牙切齒,拿它沒辦法。 angelsb這篇文章很好,希望對大家有用。我也是看到他講得很好,才翻譯過來的,水平有限,請多多指教.


System.InvalidOperationException: 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到客戶端時,卻總是不經意地跑出來了.我想,我應該花些許時間對這個問題進行一次完整的總結吧.

發生的本質是什么?

我們來認真看一下可能會發生這種異常的兩種情況

1) 你使用了超過最大的連接池連接數(默認的最大連接數是100)

在大部分應用程序中,這種情況是很少出現的. 畢竟當你使用連接池時,100個並行連接是一個非常大的數字.根據我的經驗,會造成這種異常的原因的最大可能,應該是在一個純種下打開了100個連接.



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();
    }




解決方案:如果你確定你將會使用超過100個並行連接(在同一連接字符串上),你可以增加最大連接數.

2) 連接泄漏

我個人認為的連接泄漏定義是你打開了一個連接但你沒有在你的代碼中執行close()或dispose().這范圍不僅僅是你忘記了在connection后連接后使用dispose()或close()對期進行關閉,還包括一些你已經在相關connection后寫好了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)
    }
}




這就是一個典型的例子,將這段代碼復制到visual Studio中,在 sqlconnection1.close()中設置一個斷點,編譯時可以看到他永遠沒有執行,因為ExecuteNonQurery拋出了一個異常.之后你應該可以看到恐怖的超時異常了. 在我的機子上,大約有170個連接被打開. 我曾想讓其在每次調用的時候將異常拋出來達到降低連接超時出現的機率,但當你考慮到將其部署到一個ASP.NET的應用程序的時候,任何一個泄漏都將讓你處於麻煩之中.

3)你是通過visual Studio中的sql debugging 來打開或關閉連接的

這是一個眾所周知的Bug,可以看一下下面這個鏈接
http://support.microsoft.com/default.aspx?scid=kb;en-us;830118



如何在ADO.NET2.0中判斷是否是連接泄漏

在1.0或1.1中,我們很難去判斷是否是連接泄漏,至多可以通過一些性能指標或諸如此類的工作去實現.但在ADO.NET2.0中,如果你注意到NumberOfReclaimedConnections這個玩藝兒,就可以知道你的應用程序是否是連接泄漏了.


時刻注意修復相關的連接字符串

修改相關的連接字符串可以讓你暫時翻譯”逃過”一些異常,這是非常誘人的.特別是在一個高性能消耗時,修改它就顯示更為必要了.


這里是一些讓你的應用程序能”運行良好”的非正常行為(搬起石頭砸自己的腳)

不要把Poooling=False

坦白的說,如果你將pooling設為關閉狀態,你當然不會再碰到超時異常,可怕的是你的應用程序性能將大大降低,而你的連接仍然處於泄漏狀態.

不要把Connection LifeTime=1

這不是一個能清除異常的方法,但它可能是最接近的一個解決方法.你想告訴我們的是將所有的連接超過一秒鍾的連接都通通拋棄(正常的生命周期結束應該是在connetcio.close()后).我個人認為這種方法上關閉連接池沒什么兩樣.除非你是在使用數據庫的集群,否則你不應設置連接周期來達到目的.


不要將 Connection TimeOut=40000


非常愚蠢的選擇,你這是在告訴我們在拋出一個超時異常之前,你在無限地等待一個連接轉變為可用的.幸虧在ASP.NET中將會在三分鍾之后取消一個進程.


不要將Max PoolSize=4000;
如果你將連接池的最大數設置到足夠大的時候,你最終會將這異常停止.但在另一方面,你將占用了你的應用程序中才是真正需要的巨大的連接資源,這種做法只能飲鳩止渴.



解決方案:

你需要保證你每次調用連接的同時都在使用過后通過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_
    }



FAQ:

Q:為什么要這樣做

A:使用using結構等同於Try/…/Finally{ .Dispose() ) 即使當ExecuteNonQuery會拋出一個執行錯誤時,我們都可以保證finally模塊將會執行



Q:我上面的代碼中如果沒有異常拋出的話,我可以使用close()或dispose()嗎

A:我們毫無顧忌地用他們中的任意一個,或兩個同時使用.在一個已經close或dipose()的連接中使用close()或dispose()是不會影響的

Q:Close()和Dispose()有什么不同,我應該用哪一個好?
A:它們做的是同一件事,你可以調用他們中的任意一個,或兩個同時使用.

Q:你所說的"practically the same thing”是什么意思?
A:Dispose()將會通過sqlConnection來清理相關的連接,之后執行close().它們沒有什么本質的區別,你可以通過reflector來證明這點

Q:與close()相比,connection.dispose()會將連接些移除嗎?
A:不會

---------------------------------------------------------------

我的分享:

針對"Timeout expired"這個異常,我也查閱了很多資料。在國內我們很多project都會采用MS提供的sqlhelper這個封裝類。因為這個類中有本身的缺陷所致,所以出現的"Timeout expiered"異常機率大。我在國外的一篇文章中看到的解決方案是:

將SqlHelper中的cmd.CommandTimeout="你要設置的秒數"加上去,重新編譯.



if (trans != null)
cmd.Transaction = trans;

cmd.CommandType = cmdType;
cmd.CommandTimeout = 240;


通過搜索,我找到了相關的一個代碼.
這個代碼是摘自國人的某位同行的,感謝
http://blog.csdn.net/long2006sky/archive/2007/07/09/1683459.aspx

eg:

**////
    /// 執行查詢語句,返回DataTable
    ///
    /// 查詢語句
    /// 設置查詢Timeout
    /// 用於復雜查詢
    public static DataTable GetDataTable(string SQLString,int commTime)
    ...{
        string connectionString = System.Configuration.ConfigurationManager.AppSettings["connectionString"];
        using (System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(connectionString))
        ...{
            DataTable dt = new DataTable();
            try
            ...{
                connection.Open();
                System.Data.SqlClient.SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter();
                System.Data.SqlClient.SqlCommand comm = new System.Data.SqlClient.SqlCommand(SQLString, connection);
                comm.CommandTimeout = commTime;
                da.SelectCommand = comm;
                da.Fill(dt);
            }
            catch (System.Data.SqlClient.SqlException ex)
            ...{
                throw new Exception(ex.Message);
            }
            return dt;
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM