4

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);
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
user2266175
  • 131
  • 2
  • 2
  • 7
  • 1
    Unfortunately, `UPDATES` are going to lock your table for the duration of the update. I'd say run the script in SQL directly and check your execution plan to make sure it's running as best as it can. Other than that, check this article on preventing deadlocks: http://support.microsoft.com/kb/169960 – valverij Apr 10 '13 at 13:40
  • 1
    Also, is it possible for your `static int updateVehicleLastPosition` method to be called by multiple threads at the same time? If so, you might want to wrap it in a `lock` statement, since it's updating the DB: http://msdn.microsoft.com/en-us/library/c5kehkcz(v=vs.71).aspx – valverij Apr 10 '13 at 13:43
  • @valverij - no you do not want to use a lock statement in the code. SQL Server has its own locking mechanisms which should be used instead. – Polyfun Apr 10 '13 at 13:49

2 Answers2

8

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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Polyfun
  • 9,479
  • 4
  • 31
  • 39
2

Although it's possible to check for a lock you cannot guarantee that by the time the next statement is issued some other process hasn't taken out a lock. Possible solutions in order of preference:

1) Always reference tables in the same order within trancations.

2) @ShellShock's answer

3) Trap the deadlock error and handle it.

Young Bob
  • 733
  • 3
  • 9