如何检测SqlConnection的“阻塞期”是否有效?

时间:2013-11-22 18:09:48

标签: c# ado.net

根据微软的文章(SQL Server Connection Pooling (ADO.NET)),

  

启用连接池时,如果发生超时错误或其他登录错误,将引发异常,并且后续连接尝试将在接下来的五秒内失败,即“阻塞期”。如果应用程序尝试在阻塞期内连接,则将再次抛出第一个异常。在阻塞期结束后,应用程序的另一个连接失败将导致阻塞时间段是前一个阻塞时间段的两倍。阻塞期结束后的后续失败将导致新的阻塞时段为前一个阻塞时段的两倍,最多为五分钟。

您如何检测阻止期是否有效?我会假设在尝试连接之前需要检查一些属性,以便您可以避免延长阻塞期。

3 个答案:

答案 0 :(得分:2)

不应该检查您是否处于阻塞期以避免延长它。正如上面摘录中所述,在阻塞期间连接的任何尝试都将重新抛出第一个异常,它没有提到延长阻塞期。但是,每个阻止期将是前一个

的两倍。

根据我的经验,抛出的异常(由于超时,连接泄漏等)是环境问题或未能正确关闭/处置连接。记录这些异常是一个好主意,以便您可以追踪真正的问题。

如果你确实遇到超时异常,你可以抓住它并尝试clear all the pools,但这可能是由于连接泄漏造成的。您需要确保使用using语句包装连接,这将有助于在完成连接后关闭/处置连接或发生异常。

using(SqlConnection connection = new SqlConnection("connection_string"))
{
    using(SqlCommand command = new SqlCommand())
    {

        SqlCommand command = new SqlCommand();
        command.Connection = connection;
        command.CommandType = CommandType.Text;
        command.CommandTimeout = [some timeout value]; 
        command.CommandText = "Update SomeTable Set Value = 1";

        connection.Open();

        command.ExecuteNonQuery();
    }
}

答案 1 :(得分:0)

不幸的是,没有简单的方法可以检测你是否处于ADO.NET“阻塞期”或不使用像反射一样脆弱的东西。

但是,如果您使用的是.Net 4.5或更高版本,则可以通过查看Open来检测您从OpenAsync / ClientConnectionId观察到的最后一个异常是否重复SqlException并将其与您所见过的最后一个SqlException的ID进行比较(因为异常是重复的,所以ID也是重复的。)

假设您有一个地方为单个连接字符串创建\ open SqlConnections,您可以执行以下操作:

public static class DataAccessLayer
{
    // Single connection string that all connections use
    private static readonly string _connectionString = "server=(local);integrated security=true;";

    // Stores that last observed connection if when opening a connection
    // NOTE: Using an object so that the Volatile methods will work
    private static object _lastErrorConnectionId = Guid.Empty;

    public static SqlConnection GetOpenedConnection()
    {
        try
        {
            SqlConnection connection = new SqlConnection(_connectionString);
            connection.Open();
            return connection;
        }
        catch (SqlException ex)
        {
            // Did the connection open get to the point of creating an internal connection?
            if (ex.ClientConnectionId != Guid.Empty)
            {
                // Verify that the connection id is something new
                var lastId = (Guid)Volatile.Read(ref _lastErrorConnectionId);
                if (ex.ClientConnectionId != lastId)
                {
                    // New error, save id and fall-through to re-throw
                    // NOTE: There is a small timing window here where multiple threads could end up switching this between
                    //       a duplicated id and a new id. Since this is unlikely and will only cause a few additional exceptions to be
                    //       thrown\logged, there isn't a large need for a lock here.
                    Volatile.Write(ref _lastErrorConnectionId, (object)ex.ClientConnectionId);
                }
                else
                {
                    // Duplicate error
                    throw new DuplicatedConnectionOpenException(_connectionString, ex);
                }
            }

            // If we are here, then this is a new exception
            throw;
        }
    }
}

public class DuplicatedConnectionOpenException : Exception
{
    public string ConnectionString { get; private set; }

    internal DuplicatedConnectionOpenException(string connectionString, SqlException innerException)
        : base("Hit the connection pool block-out period and a duplicated SqlException was thrown", innerException)
    {
        ConnectionString = connectionString;
    }
}

现在,如果您拨打GetOpenedConnection并且看到DuplicatedConnectionOpenException被抛出,您就会知道您已经点击了“阻止期”。

注意:我在这里使用Volatile Read / Write而非锁定,因为我选择了更好的性能而不是100%准确处于“阻止期” ”。如果您更喜欢准确性,可以使用lock代替。

此外,我的代码在SqlConnection上可以作为扩展方法使用,并且可以处理多个连接字符串,但是它的性能要差得多,因为它使用ConcurrentDictionary将连接字符串映射到连接ID

答案 2 :(得分:0)

除ClientConnectionId字段外,

SqlException.Message也将是 reference-equal 。也就是说,对于在“阻塞期”内失败的连接,将返回缓存的字符串。

但是,这也是一个实施细节,可能会有所改变。