Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get rid of deadlock in a SQL Server 2005 and C# application?

I have a code in C# for windows service which is mainly responsible to update records in a table in my database, but I get always a lot of errors in my log, all errors are about deadlock of resource,

This is the error:

System.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 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.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource1 completion, Int32 timeout, Task& task, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() at WheelTrackListener.DataAccess.SQLDBA.ExecuteNQuery(SqlCommand cmd, Boolean isShowError, ConnectionStringType CountryCode, String deviceID, Int32 retry, String functionCallName) ClientConnectionId:e45e4cf1-a113-46b7-b9b5-dc5ee8111406

Now, I want to ask, can I make a try or check if resource is locked? and if locked how to wait update until it is released?

Here is my current code:

public static int updateVehicleLastPosition(string UTCDate, string UTC_Time, 
              string NS_Indicator, string Latitude, string EWIndicator, 
              string Longtitude, string Speed, string Processed, 
              string Near_ByLocation, string Near_ByLocation_AR, 
              string Gis_dataID, string address, string ar_adress, string Device_ID)
{
        SQLMethods sql = new SQLMethods();
        SqlCommand cmd = sql.cmdUpdateVehicleLastPosition(UTCDate, UTC_Time, NS_Indicator, Latitude, EWIndicator, Longtitude, Speed, Processed, Near_ByLocation, Near_ByLocation_AR, Gis_dataID, address, ar_adress, Device_ID);
        SQLDBA sqlDBA = new SQLDBA();
        return sqlDBA.ExecuteNQuery(cmd, true, ConnectionStringType.OMN, Device_ID, 10, "updateVehicleLastPosition");
}

public SqlCommand cmdUpdateVehicleLastPosition(string UTCDate, string UTC_Time, 
                      string NS_Indicator, string Latitude, string EWIndicator, 
                      string Longtitude, string Speed, string Processed, 
                      string Near_ByLocation, string Near_ByLocation_AR, 
                      string Gis_dataID, string address, string ar_adress, 
                      string Device_ID)
 {
        string sql = "UPDATE CTS_VehicleLastPosition SET [UTCDate] = @UTCDate, [UTC_Time] = @UTC_Time, [NS_Indicator] = @NS_Indicator, [Latitude] = @Latitude, [EWIndicator] = @EWIndicator, [Longtitude] = @Longtitude, [Speed] = @Speed, [Processed] = @Processed, [Near_ByLocation] = @Near_ByLocation, [Near_ByLocation_AR] = @Near_ByLocation_AR, [Gis_dataID] = @Gis_dataID, [address] = @address, [ar_adress] = @ar_adress WHERE [Device_ID] = @Device_ID";

        SqlCommand cmd = new SqlCommand(sql);
        cmd.Parameters.AddWithValue("@UTCDate", UTCDate);
        cmd.Parameters.AddWithValue("@UTC_Time", UTC_Time);
        cmd.Parameters.AddWithValue("@NS_Indicator", NS_Indicator);
        cmd.Parameters.AddWithValue("@Latitude", Latitude);
        cmd.Parameters.AddWithValue("@EWIndicator", EWIndicator);
        cmd.Parameters.AddWithValue("@Longtitude", Longtitude);
        cmd.Parameters.AddWithValue("@Speed", Speed);
        cmd.Parameters.AddWithValue("@Processed", Processed);
        cmd.Parameters.AddWithValue("@Near_ByLocation", Near_ByLocation);
        cmd.Parameters.AddWithValue("@Near_ByLocation_AR", Near_ByLocation_AR);
        cmd.Parameters.AddWithValue("@Gis_dataID", Gis_dataID);
        cmd.Parameters.AddWithValue("@address", address);
        cmd.Parameters.AddWithValue("@ar_adress", ar_adress);
        cmd.Parameters.AddWithValue("@Device_ID", Device_ID);
        return cmd;
}

public int ExecuteNQuery(SqlCommand cmd, bool isShowError, 
                    DataAccess.ConnectionStringType CountryCode, string deviceID, 
                    int retry, string functionCallName)
{
        ConnectionManager Connection = new ConnectionManager();
        try
        {
            Connection.GetConnection(CountryCode);
            if ((Connection.con == null) || (Connection.con.State != ConnectionState.Open))
            {
                if (retry <= 0) return 0;
                else return ExecuteNQuery(cmd, isShowError, CountryCode, deviceID, retry - 1, functionCallName);
            }
            int rowsAffected = 0;
            cmd.Connection = Connection.con;
            rowsAffected = cmd.ExecuteNonQuery();
            return rowsAffected;
        }
        catch (SqlException sqlexception)
        {
            if (isShowError)
                LEAMRALogger.Logger.WriteByDate("Logs\\SQLDBA\\" + functionCallName + "\\" + String.Format("{0:dd-MM-yyyy}", DateTime.Now), "SQLDBA", "SQLDBA_ERROR", "ExecuteNQuery Function: [deviceID: " + deviceID + " | retry: " + retry + "] " + sqlexception.ToString());
        }
        catch (Exception ex)
        {
            if (isShowError)
                LEAMRALogger.Logger.WriteByDate("Logs\\SQLDBA\\" + functionCallName + "\\" + String.Format("{0:dd-MM-yyyy}", DateTime.Now), "SQLDBA", "SQLDBA_ERROR", "ExecuteNQuery Function: [deviceID: " + deviceID + " | retry: " + retry + "] " + ex.ToString());
        }
        finally
        {
            if ((Connection.con != null) && (Connection.con.State == ConnectionState.Open))
            {
                Connection.con.Close();
                Connection.con.Dispose();
            }

            GC.Collect();
        }
        if (retry <= 0) return 0;
        else return ExecuteNQuery(cmd, isShowError, CountryCode, deviceID, retry - 1, functionCallName);
}
like image 822
user2266175 Avatar asked Dec 26 '22 05:12

user2266175


1 Answers

I can't see any explicit transaction scope in your code, so I do not know what locks are already in place when you are doing your update; also it is not clear what isolation level you are using. But the most common scenario in this type of situation is that earlier in the same transaction you have issued a select (read lock) on the same rows that you are trying to update later. This will cause a lock escalation, and can result in a deadlock if two transactions are trying to do the same thing:

  1. Transaction A: select with read lock
  2. Transaction B: select with read lock
  3. Transaction A: update - wants to escalate its read lock to a write lock, but has to wait for transaction B to release its read lock
  4. Transaction B: update - wants to escalate its read lock to a write lock, but has to wait for transaction A to release its read lock.

Bingo! deadlock as both A and B are waiting on each other to release their existing read locks before they can do their update.

To prevent this you need an updlock hint in your select, e.g.,

select * from table with (updlock) where blah blah

This will ensure your select uses a write lock instead of a read lock, which will prevent lock escalation between concurrent transactions.

like image 60
Polyfun Avatar answered Feb 14 '23 00:02

Polyfun