即使调用应用回滚,SQL Server SP也会提交事务

时间:2018-07-31 23:36:07

标签: c# sql-server transactions

我有一个插入到表中的存储过程。此插入文件包装在事务中。同样,调用应用程序将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如何提交?

0 个答案:

没有答案