C# 为什么这个脚本经常导致SQL死锁?

C# 为什么这个脚本经常导致SQL死锁?,c#,sql,sql-server,dapper,database-deadlocks,C#,Sql,Sql Server,Dapper,Database Deadlocks,我在Azure中每小时运行一个web作业中的后台任务。有时(似乎有50%多一点的时间),代码会在这段特定代码上阻塞(死锁错误): foreach (var ownerToProcess in activeOwnersWithMessageArchiving) { foreach (var extension in extensions) { using (var db = new SqlConnection(connectionString)) {

我在Azure中每小时运行一个web作业中的后台任务。有时(似乎有50%多一点的时间),代码会在这段特定代码上阻塞(死锁错误):

foreach (var ownerToProcess in activeOwnersWithMessageArchiving)
{
    foreach (var extension in extensions)
    {
        using (var db = new SqlConnection(connectionString))
        {
            db.Execute(@"
UPDATE T_MESSAGESTARTER 
   SET Started=@started,Completed=NULL 
 WHERE OwnerId=@ownerId AND ExtensionId=@extensionId;

if @@ROWCOUNT=0
  INSERT INTO T_MESSAGESTARTER (OwnerId,ExtensionId,Started) 
  VALUES (@ownerId,@extensionId,@started)
", new { ownerId = ownerToProcess, extensionId = extension, started = DateTimeOffset.Now });
        }
    }
}
这是一个简单的update/insert语句。我“相信”我也在使用行级阻塞。这不在事务内部。此外,顶级中大约有60个
ownerToProcess
项。其中每一个在内部循环中都有5-60个
扩展
项(在上面的代码中)。这使得在每次运行中大约有4000次执行此SQL语句。每个
@owner
/
@extension
组合(在WHERE子句中)都是唯一的

有时它会一直运行而不出错。但有时我会在执行SQL语句时出现死锁错误。这是什么原因造成的?是因为我在SQL语句中有
UPDATE/INSERT
结构吗?还是Dapper在做一些有趣的事情


另一件需要注意的事情是:所讨论的
T\u MESSAGESTARTER
表没有主键。这可能会导致此问题吗?

它不需要是主键,但ownerId和extensionId上的复合唯一索引(理想情况下是聚集的)将优化更新查询。这可能会缓解死锁(取决于所涉及的过程)通过触摸最少的数据量。

即使没有实际更新行(当行不存在时),更新也会锁定“表”。 根据并发性的不同,以下最有可能是安全的(如果两个进程永远不会处理同一个所有者和扩展,那么它就可以工作)


无需在double foreach迭代中打开连接

using (var db = new SqlConnection(connectionString))
{
  foreach (var ownerToProcess in activeOwnersWithMessageArchiving)
  {
    foreach (var extension in extensions)
    {

            db.Execute(@"
UPDATE T_MESSAGESTARTER 
   SET Started=@started,Completed=NULL 
 WHERE OwnerId=@ownerId AND ExtensionId=@extensionId;

if @@ROWCOUNT=0
  INSERT INTO T_MESSAGESTARTER (OwnerId,ExtensionId,Started) 
  VALUES (@ownerId,@extensionId,@started)
", new { ownerId = ownerToProcess, extensionId = extension, started = DateTimeOffset.Now });
    }
  }
}
仅供参考,该声明也存在。

这是一个标准的SQL,它也可以做UpSerts。

我们需要看到你的死锁图来知道什么资源是死锁和为什么。它不是可以从代码中确定的东西。建议:考虑把这两行移动到一个存储过程中,并在存储过程中启用<代码> AutoCytoSpMyto= < <代码> >。我敢打赌。它很有可能消除死锁。容易做到;易于经验验证;)@DaleK我不是SQL专家,不幸的是我不知道如何生成死锁图。@MattSpinks如果你用谷歌搜索一个死锁图,你会发现有一个相当直接的查询来获取它-我身上没有它。好的,找到了吗
using (var db = new SqlConnection(connectionString))
{
  foreach (var ownerToProcess in activeOwnersWithMessageArchiving)
  {
    foreach (var extension in extensions)
    {

            db.Execute(@"
UPDATE T_MESSAGESTARTER 
   SET Started=@started,Completed=NULL 
 WHERE OwnerId=@ownerId AND ExtensionId=@extensionId;

if @@ROWCOUNT=0
  INSERT INTO T_MESSAGESTARTER (OwnerId,ExtensionId,Started) 
  VALUES (@ownerId,@extensionId,@started)
", new { ownerId = ownerToProcess, extensionId = extension, started = DateTimeOffset.Now });
    }
  }
}