我有一个插入到表中的存储过程。此插入文件包装在事务中。同样,调用应用程序将SQlCommand调用包装到此SQLTransaction中的此存储过程中。如果一切顺利,则将提交事务,如果未调用,则事务将被提交,并且存储过程将回滚。 失败后,应用程序将通过再次调用SP重试插入。
这是一个简化的数据库代码:
create table Employee (
[Key] int identity(1,1) not null,
[EmployeeId] varchar(100) not null,
[EmployeeName] varchar(100) not null,
constraint Employee_PK primary key clustered ( [Key] asc),
constraint Employee_Unq_EmployeeId unique( [EmployeeId] )
)
go
create procedure InsertEmployee
@employeeId varchar(100),
@employeeName varchar(100)
as
begin
declare @key int;
begin tran
insert into employee values (@employeeId, @employeeName);
set @key = @@identity;
if (@@error <> 0) GOTO SQLError
commit
select @key as EmployeeKey;
return 0;
SQLError:
rollback
return @@error
end
这是调用应用程序的简化版本:
static void InsertEmployee(string employeeId, string employeeName)
{
bool done = false;
int retryCount = 0;
while(!done)
{
done = CallInsertEmployeeStoredProc(employeeId, employeeName);
if(!done)
{
retryCount++;
if (retryCount >= MAXALLOWEDRETRIES)
throw new ApplicationException("Unable to insert employee");
Thread.Sleep(TimeSpan.FromSeconds(20));
}
}
}
private static bool CallInsertEmployeeStoredProc(string employeeId, string employeeName)
{
using (SqlConnection conn = new SqlConnection(CONNSTRING))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
using (SqlTransaction tran = conn.BeginTransaction())
{
cmd.Connection = conn;
cmd.CommandText = "InsertEmployee";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Transaction = tran;
var eId = cmd.Parameters.Add("@employeeId", SqlDbType.VarChar);
eId.Value = employeeId;
eId.Direction = ParameterDirection.Input;
var eName = cmd.Parameters.Add("@employeeName", SqlDbType.VarChar);
eName.Value = employeeName;
eName.Direction = ParameterDirection.Input;
try
{
cmd.ExecuteScalar();
tran.Commit();
return true;
}
catch (Exception ex)
{
try
{
tran.Rollback();
}
catch (Exception trex)
{
Console.WriteLine(trex);
}
Console.WriteLine(ex);
return false;
}
}
}
}
}
但是,有时存储过程会超时,并且我会看到最初的错误集:
InvalidOperationException This SqlTransaction has completed; it is no longer usable.
at [ at System.Data.SqlClient.SqlTransaction.ZombieCheck()
at System.Data.SqlClient.SqlTransaction.Rollback()
at MyTestApp.Program.CallInsertEmployeeStoredProc(string employeeId, string employeeName)]
SqlException Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Win32Exception The wait operation timed out
at [ MyTestApp.Program.CallInsertEmployeeStoredProc(string employeeId, string employeeName)]
但是,当应用程序退出插入时,也就是说,由于存储过程已经提交了雇员插入操作,因此它遇到了唯一键冲突错误:
System.Data.SqlClient.SqlException (0x80131904): Violation of UNIQUE KEY constraint 'Employee_Unq_EmployeeId'. Cannot insert duplicate key in object 'dbo.Employee'. The duplicate key value is(Emp002).
The statement has been terminated.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteScalar()
at MyTestApp.Program.CallInsertEmployeeStoredProc(String employeeId, String employeeName)
ClientConnectionId:254fe674-9339-45a7-bb79-e3305bc3f850
Error Number:2627,State:1,Class:14
这怎么可能发生?当调用应用尝试回滚并失败时,SQLServer如何提交?