我有一个非常奇怪的问题,只有当有问题的代码处于高负载情况时才会出现。我的ASP.NET Web客户端C#代码调用具有OUTPUT
参数的T-SQL存储过程。
在高负载下,返回的数据有时不会返回到调用C#代码。我已将所有相关代码提取到下面的示例中;
存储过程:
CREATE PROCEDURE GetLoginName
@LoginId BIGINT,
@LoginName VARCHAR(50) OUTPUT
AS
SET NOCOUNT ON
SELECT @LoginName = LoginName
FROM Logins
WHERE Id = @LoginId
SET NOCOUNT OFF
GO
数据库基类:
public class DatabaseContextBase : IDisposable
{
private SqlConnection _connection;
private string _connectionString;
private SqlInt32 _returnValue;
private int _commandTimeout;
private static int _maxDatabaseExecuteAttempts = 3;
private static int s_commandTimeout = 30;
protected DBContextBase()
{
// try and get the connection string off the identity first...
string connectionString = GetConnectionStringFromIdentity();
if(connectionString != null)
{
ConstructionHelper(connectionString, s_commandTimeout);
}
else
{
// use the initialised static connection string, and call the other overload
// of the constructor
ConstructionHelper(s_connectionString, s_commandTimeout);
}
}
private void ConstructionHelper( string connectionString, int commandTimeout )
{
// store the connection string in a member var.
_connectionString = connectionString;
// store the timeout in a member var.
_commandTimeout = commandTimeout;
}
public static string GetConnectionStringFromIdentity()
{
IIdentity identity = Thread.CurrentPrincipal.Identity as wxyz.Security.wxyzIdentityBase;
string connectionString = null;
if(identity != null)
{
connectionString = ((wxyz.Security.wxyzIdentityBase) identity ).ConnectionString;
}
return connectionString;
}
public void Dispose()
{
if (_connection.State != ConnectionState.Closed)
{
_connection.Close();
}
_connection.Dispose();
_connection = null;
}
protected void ExecuteNonQuery(SqlCommand command)
{
SqlConnection con = this.Connection;
lock (con)
{
if (con.State != ConnectionState.Open)
{
con.Open();
}
// don't need a try catch as this is only ever called from another method in this
// class which will wrap it.
command.Connection = con;
command.Transaction = _transaction;
command.CommandTimeout = _commandTimeout;
for (int currentAttempt = 1; currentAttempt == _maxDatabaseExecuteAttempts; currentAttempt++)
{
try
{
// do it
command.ExecuteNonQuery();
// done, exit loop
break;
}
catch (SqlException sex)
{
HandleDatabaseExceptions(currentAttempt, sex, command.CommandText);
}
}
}
}
protected void HandleDatabaseExceptions(int currentAttempt, SqlException sqlException, string sqlCommandName)
{
if (DataExceptionUtilities.IsDeadlockError(sqlException))
{
if (!this.IsInTransaction)
{
// Not in a transaction and a deadlock detected.
// If we have not exceeded our maximum number of attemps, then try to execute the SQL query again.
string deadlockMessage = string.Format("Deadlock occured in attempt {0} for '{1}':", currentAttempt, sqlCommandName);
Logging.Write(new ErrorLogEntry(deadlockMessage, sqlException));
if (currentAttempt == DBContextBase.MaxDatabaseExecuteAttempts)
{
// This was the last attempt so throw the exception
throw sqlException;
}
// Wait for a short time before trying again
WaitShortAmountOfTime();
}
else
{
// We're in a transaction, then the calling code needs to handle the deadlock
string message = string.Format("Deadlock occured in transaction for '{0}':", sqlCommandName);
throw new DataDeadlockException(message, sqlException);
}
}
else if (this.IsInTransaction && DataExceptionUtilities.IsTimeoutError(sqlException))
{
// We're in a transaction and the calling code needs to handle the timeout
string message = string.Format("Timeout occured in transaction for '{0}':", sqlCommandName);
// Raise a Deadlock exception and the calling code will rollback the transaction
throw new DataDeadlockException(message, sqlException);
}
else
{
// Something else has gone wrong
throw sqlException;
}
}
/// <summary>
/// get the SqlConnection object owned by this database (already connected to db)
/// </summary>
public SqlConnection Connection
{
get {
// check whether we've got a connection string (from either identity or static initialise)
if ( _connectionString == null )
{
throw new ArgumentNullException( "connectionString", "Connection string not set" );
}
if ( _connection != null )
{
return _connection;
}
else
{
_connection = new SqlConnection( _connectionString );
return _connection;
}
}
}
/// <summary>
/// Return value from executed stored procedure
/// </summary>
public SqlInt32 ReturnValue
{
get { return _returnValue; }
set { _returnValue = value; }
}
}
数据库访问类:
public class AuthenticationDBCommands
{
public static SqlCommand GetLoginName()
{
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("GetLoginName");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@RETURN_VALUE", System.Data.SqlDbType.Int, 0, System.Data.ParameterDirection.ReturnValue, false, 0, 0, "RETURN_VALUE", System.Data.DataRowVersion.Current, SqlInt32.Null));
cmd.Parameters.Add(new SqlParameter("@LoginId", SqlDbType.BigInt, 8, ParameterDirection.Input, false, 0, 0, "LoginId", DataRowVersion.Current, SqlInt64.Null));
cmd.Parameters.Add(new SqlParameter("@LoginName", SqlDbType.VarChar, 50, ParameterDirection.InputOutput,false, 0, 0, "LoginName", DataRowVersion.Current, SqlString.Null));
return cmd;
}
}
public class AuthenticationDBContext : DatabaseContextBase
{
public AuthenticationDBContext() : base()
{
}
public void GetLoginName(SqlInt64 LoginId, ref SqlString LoginName)
{
SqlCommand cmd = AuthenticationDBCommands.GetLoginName();
cmd.Parameters[1].Value = LoginId;
cmd.Parameters[2].Value = LoginName;
base.ExecuteNonQuery(cmd);
base.ReturnValue = (SqlInt32) cmd.Parameters[0].Value;
LoginName = (SqlString)(cmd.Parameters[2].Value);
}
}
因此,当它在ASP.NET Web客户端中使用时,它看起来像这样:
protected string GetLoginName(long loginId)
{
SqlString loginName = SqlString.Null;
using (AuthenticationDBContext dbc = new AuthenticationDBContext())
{
dbc.GetLoginName(loginId, ref loginName);
}
return loginName.Value;
}
正如您所看到的,这是相当标准的东西。但是当许多不同的用户快速连续调用AuthenticationDBContext.GetLoginName()方法时,loginName对象有时 null。
当SqlCommand.ExecuteNonQuery()无法返回任何数据时,它不会抛出异常。
我测试了SQL,它总是找到一个值(我已经将@LoginName插入到表中,它永远不会为null)。所以问题发生在SqlCommand.ExecuteNonQuery();
之后或之中正如我所说,这是它发生的一个例子。实际上,许多不同的存储过程都没有返回数据。
同样值得一提的是,当有问题的Web客户端负载很重时,在IIS中共享应用程序池的其他Web客户端不会受到影响。
我正在使用.NET 4.5,我的数据库在SQL Server 2008上。
以前有人见过这样的事吗? 任何人都可以推荐任何改变吗?
提前致谢,
马特
更新。谢谢你们的评论。我对DatabaseContextBase类进行了以下更改。
private void ExecuteNonQueryImpl(SqlCommand command)
{
object _lockObject = new object();
lock (_lockObject)
{
SqlConnection con = this.GetConnection();
if (con.State != ConnectionState.Open)
{
con.Open();
}
// don't need a try catch as this is only ever called from another method in this
// class which will wrap it.
command.Connection = con;
command.Transaction = _transaction;
command.CommandTimeout = _commandTimeout;
for (int currentAttempt = 1; currentAttempt <= _maxDatabaseExecuteAttempts; currentAttempt++)
{
try
{
// do it
command.ExecuteNonQuery();
// done, exit loop
break;
}
catch (SqlException sex)
{
HandleDatabaseExceptions(currentAttempt, sex, command.CommandText);
}
}
if (!this.IsInTransaction)
{
con.Close();
}
}
}
public SqlConnection GetConnection()
{
if (this.IsInTransaction)
{
return this.Connection;
}
else
{
// check whether we've got a connection string (from either identity or static initialise)
if ( _connectionString == null )
{
string exceptionMessage = Language.Translate("DbContextNotInitialized");
throw new ArgumentNullException( "connectionString", exceptionMessage );
}
return new SqlConnection(_connectionString);
}
}
但是,在负载测试中,数据有时仍会返回null。 Web客户端不在事务中工作,因此每次调用时都会创建,打开和关闭新的SqlConnection对象。 (还有其他共享DatabaseContextBase类的代码区域,它们在事务中起作用,因此需要原始连接属性)
我想再次提一下,我确信存储过程正常,因为我已将@LoginName值插入表中,并且它永远不会为空。
谢谢, 马特
答案 0 :(得分:0)
你的&#34; for循环&#34;定义不正确。
for (int currentAttempt = 1; currentAttempt == _maxDatabaseExecuteAttempts; currentAttempt++)
这会将currentAttempt初始化为1,运行循环,递增currentAttempt,然后检查currentAttempt是否等于3,它不是,并退出循环。我想你想要的是
for (int currentAttempt = 1; currentAttempt <= _maxDatabaseExecuteAttempts; currentAttempt++)