根据微软的文章(SQL Server连接池(ADO.NET)),
启用连接池后,如果发生超时错误或其他登录错误,将引发异常,随后的连接尝试将在接下来的五秒钟内失败,即"阻塞期"。如果应用程序试图在阻塞期内连接,则会再次引发第一个异常。阻塞期结束后,应用程序的另一次连接失败将导致阻塞期是前一个阻塞期的两倍。阻塞期结束后的后续故障将导致新的阻塞期,该阻塞期是前一个阻塞期的两倍,最长可达五分钟。
您将如何检测到阻塞期处于活动状态?我假设在尝试连接之前要检查一些属性,这样就可以避免延长阻塞期。
不需要检查您是否处于阻塞期以避免延长阻塞期。正如上面摘录的那样,在阻塞期内任何连接尝试都会重新抛出第一个异常,这与延长阻塞期无关。但是,每个新的阻塞周期将是前一个的两倍长。
根据我的经验,抛出的异常(由于超时、连接泄漏等)要么是环境问题,要么是未能正确关闭/处理连接。记录这些异常是个好主意,这样您就可以追踪到真正的问题。
如果你确实不断遇到超时异常,你可以抓住它并尝试清除所有池,但这很可能是由于连接泄漏。您需要确保使用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();
}
}
除了ClientConnectionId字段外,
SqlException.Message也将是reference equal。也就是说,对于在"阻塞期"内失败的连接,将返回缓存的字符串。
然而,这也是一个实现细节,可能会发生变化。
不幸的是,没有简单的方法可以检测您是否处于ADO.NET"阻塞期"(而不需要求助于像反射这样脆弱的东西)。
但是,如果您使用.Net 4.5或更高版本,则可以通过查看SqlException
的ClientConnectionId
并将其与您看到的最后一个SqlException
的id进行比较来检测您从Open
/OpenAsync
观察到的最后一次异常是否重复(由于异常重复,因此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
// thrownlogged, 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。