事务(进程 ID 84)与另一个进程在锁资源上死锁,并已被选为死锁牺牲品

Posted

技术标签:

【中文标题】事务(进程 ID 84)与另一个进程在锁资源上死锁,并已被选为死锁牺牲品【英文标题】:Transaction (Process ID 84) was deadlocked on lock resources with another process and has been chosen as the deadlock victim 【发布时间】:2012-06-19 09:11:28 【问题描述】:

我开发了一个监控应用程序。所以我使用了一个 Timer 函数来检查 SQL 表中的一些值。

虽然有这么多函数,但它会为一个名为 getLogEntry() 的函数提供以下错误

message>Transaction (Process ID 84) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.</message>
<innerMessage>
</innerMessage>
<source>.Net SqlClient Data Provider</source>
<stackTrace>at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlDataReader.HasMoreRows()
   at System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
   at ShiftAlertSystem.DBAccess.getLogEntry(Int32 nEventLogIdn, connections cn)</stackTrace>
    <createdAt>2012/06/18 13:10:47</createdAt>

这是函数的实现

public LogEntry getLogEntry(int nEventLogIdn, connections cn)
    
        lock (_objLock)
        
            LogEntry lgEntObj = new LogEntry();
             SqlConnection NewCon3 = new SqlConnection();
             SqlCommand newCmd2 = null;
             SqlDataReader dr = null;

             try
             


                 string connectString;
                 // Configuration config = ConfigurationManager.u
                 string DataSource = cryptIT.Decrypt(cn.DataSource_bio);
                 string initialCatalog = cryptIT.Decrypt(cn.InitialCatalog_bio);
                 string user = cryptIT.Decrypt(cn.user_bio);
                 string password = cryptIT.Decrypt(cn.password_bio);
                 bool intergratedSecurity = cn.IntegratedSecurity_bio;

                 if (intergratedSecurity)
                 
                     connectString = "Data Source=" + DataSource + ";Initial Catalog=" + initialCatalog + ";Integrated Security=True";
                 
                 else
                 
                     connectString = "Data Source=" + DataSource + ";Initial Catalog=" + initialCatalog + ";User ID=" + user + ";Password=" + password;
                 

                 NewCon3 = new SqlConnection(connectString);
                 NewCon3.Open();



                 newCmd2 = NewCon3.CreateCommand();
                 newCmd2.Connection = NewCon3;
                 newCmd2.CommandType = CommandType.Text;
                 newCmd2.CommandText = @"
                                 SELECT [nUserID]
                                        ,[sUserName]
                                        ,dateadd(s,[nDateTime],'1970/1/1') AS LogDateTime
                                        ,[nEventIdn]
                                        ,[nTNAEvent]
                                        ,[TB_READER].[nReaderIdn]
                                        ,[sName]
                                 FROM 
                                        [TB_EVENT_LOG]
                                        ,[TB_USER]
                                        ,[TB_READER]
                                WHERE 

                                        [nEventLogIdn] = " + nEventLogIdn +
                                         @" AND
                                        [TB_EVENT_LOG].[nUserID] = [TB_USER].[sUserID]
                                        AND
                                        [nFlag]= 1
                                        AND
                                        [TB_EVENT_LOG].[nReaderIdn]=[TB_READER].[nReaderIdn]"
                                         ;
                 dr = newCmd2.ExecuteReader();

                 if (dr != null && dr.Read())
                 
                     lgEntObj.nUserID = dr.GetInt32(0);
                     lgEntObj.nUserName = dr.GetString(1);
                     lgEntObj.LogDateTime = dr.GetDateTime(2);
                     lgEntObj.nEventIdn = dr.GetInt32(3);
                     lgEntObj.nTNAEvent = dr.GetInt16(4);
                     lgEntObj.nReaderIdn = dr.GetInt32(5);
                     lgEntObj.sName = dr.GetString(6);
                 
                 dr.Close();
                 newCmd2.Dispose();
                 // NewCon.Close();
                 NewCon3.Close();

                 return lgEntObj;
             
             catch (Exception exc)
             
                 CenUtility.ErrorLog.CreateLog(exc);
                 return null;
             

             finally
             
                 if (dr != null)
                     dr.Close(); 

                 if(newCmd2 != null)
                     newCmd2.Dispose();


                     NewCon3.Close();


             


        
    

提前致谢

【问题讨论】:

您可能需要考虑在此答案中提出的建议:***.com/questions/2382410/…。如果原始查询死锁,我们已成功实现查询重试。 另外,你写了多少日志条目?如果你写了很多,可能是你只是用大量的 INSERTS 来阻碍 SELECT。 此应用程序不会将任何内容写入这些表中,但另一个软件会将数据写入这些表中。 请获取死锁图并将其添加到您的问题中。如果您在 2008 you can get this from the default Extended Events trace 上,否则您将需要设置跟踪来捕获它。 【参考方案1】:

您可能需要参考此question 以获得更多有用的建议。

我使用以下模式进行数据库重试;在这种情况下,我们返回一个 DataTable 但无论如何模式都是相同的;根据 SqlException Number 检测到 SqlDeadlock 或 Timeout,然后重试,最多 n 次。

    public DataTable DoSomeSql(int retryCount = 1)
    
        try
        
            //Run Stored Proc/Adhoc SQL here

        
        catch (SqlException sqlEx)
        
            if (retryCount == MAX_RETRY_COUNT) //5, 7, Whatever
            
                log.Error("Unable to DoSomeSql, reached maximum number of retries.");
                throw;
            

            switch (sqlEx.Number)
            
                case DBConstants.SQL_DEADLOCK_ERROR_CODE: //1205
                    log.Warn("DoSomeSql was deadlocked, will try again.");
                    break;
                case DBConstants.SQL_TIMEOUT_ERROR_CODE: //-2
                    log.Warn("DoSomeSql was timedout, will try again.");
                    break;
                default:
                    log.WarnFormat(buf.ToString(), sqlEx);
                    break;
            

            System.Threading.Thread.Sleep(1000); //Can also use Math.Rand for a random interval of time
            return DoSomeSql(asOfDate, ++retryCount);
        
    

【讨论】:

我希望你能监控到这一点。例如,如果每百万次查询或每月重试一次死锁,那就没问题了。如果您每分钟或每隔一个查询得到 1 个,那么您就有麻烦了,应该解决原因而不是解决它... @Dems 有时你会遇到死锁,但你不希望你的应用程序因此而死;重试为您赢得了调查问题根本原因所需的时间;请注意,我确实建议阅读第一个链接 :-) 有时您还会因为系统繁忙而出现死锁或超时。 我不是说不要这样做,我只是说要监控它。正如我所说,如果它不常见,那么你很好。但是如果它是正常的,那么你就有问题并且你已经强迫它进入静默失败 - 在那个时间点对应用程序有好处,但这意味着你需要某种方式来监控它是否经常发生以至于你真的需要解决根本原因,而不仅仅是它的影响。如果您不进行监控,那么您甚至都不知道有什么需要调查的。 @Dems 实际上,在高峰负载期间,我们每小时会收到 20 或 30 次。这种方法给了我足够的时间来保持系统运行,还可以按照您的建议调查根本原因。也修复了它:-)我完全同意你的看法! 可能重试 SqlException.NumberSqlConnectionBroken = -1, SqlTimeout = -2, SqlOutOfMemory = 701, SqlOutOfLocks = 1204, SqlDeadlockVictim = 1205, SqlLockRequestTimeout = 1222, SqlTimeoutWaitingForMemoryResource = 8645, SqlLowMemoryCondition = 8651, SqlWordbreakerTimeout = 30053【参考方案2】:

您的查询与另一个查询陷入僵局。另一个查询很可能是 insertupdatedelete 查询,因为单独使用 select 不会导致死锁。

如果你不太在意一致性,可以使用with (nolock)提示:

FROM 
    [TB_EVENT_LOG] with (nolock)
    ,[TB_USER] with (nolock)
    ,[TB_READER] with (nolock)

这将导致您的查询不加锁。没有锁的查询不会导致死锁。缺点是当它与修改查询同时运行时,它可能会返回不一致的数据。

【讨论】:

请注意,如果锁已升级为表锁,这将不起作用;见msdn.microsoft.com/en-us/library/ms187373.aspx。 @dash:那篇文章仅适用于 DDL,例如 alter table,它获得了 Sch-M(模式修改)锁。 with (nolock) 会愉快地阅读 with (tablockx) "在 SQL Server 的未来版本中将删除对在 FROM 子句中使用适用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示的支持。避免在在新的开发工作中,并计划修改当前使用它们的应用程序。”也在其中。我不是 100% 确定它会在所有情况下通读事务;我遇到过 nolock 没有工作的情况,因为 tablelock 已经到位。 另外,从文章的标题:适用于:删除插入选择更新合并我并不是真的不同意你(因此只是一个评论),只是我发现了行为(NOLOCK) 不可靠,正如@Dems 提到的,最好解决根本问题。

以上是关于事务(进程 ID 84)与另一个进程在锁资源上死锁,并已被选为死锁牺牲品的主要内容,如果未能解决你的问题,请参考以下文章

事务 ( 进程 ID 60) 与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品。请重新运行 该事务。

SqlException 事务(进程 ID 159)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。

SqlException 事务(进程 ID 159)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。

事务(进程 ID 64)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。

事务(进程 ID)在锁定资源上与另一个进程死锁,并已被选为死锁牺牲品。在 sql server 2014 中

sqlserver之事务(进程 ID 70)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。