gpt4 book ai didi

.net - 为什么这两条sql语句会死锁? (死锁图+详细信息包括在内)

转载 作者:行者123 更新时间:2023-12-03 03:09:50 25 4
gpt4 key购买 nike

我有以下死锁图,它描述了两个相互死锁的 sql 语句。我只是不确定如何分析这个问题,然后修复我的 sql 代码以防止这种情况发生。

主死锁图

alt text http://img140.imageshack.us/img140/6193/deadlock1.png
Click here for a bigger image.

左侧,细节

alt text http://img715.imageshack.us/img715/3999/deadlock2.png
Click here for a bigger image.

右侧,细节

alt text http://img686.imageshack.us/img686/5097/deadlock3.png
Click here for a bigger image.

原始死锁架构 xml 文件

Click here to download the xml file .

表模式

alt text http://img509.imageshack.us/img509/5843/deadlockschema.png

日志条目表详细信息

alt text http://img28.imageshack.us/img28/9732/deadlocklogentriestable.png

连接的客户端表详细信息

alt text http://img11.imageshack.us/img11/7681/deadlockconnectedclient.png

代码在做什么?

我正在同时读取多个文件(例如,在此示例中可以说是 3 个)。每个文件包含不同的数据,但数据类型相同。然后我将数据插入 LogEntries表,然后(如果需要)我从 ConnectedClients 中插入或删除某些内容 table 。

这是我的 sql 代码。

using (TransactionScope transactionScope = new TransactionScope())
{
_logEntryRepository.InsertOrUpdate(logEntry);

// Now, if this log entry was a NewConnection or an LostConnection, then we need to make sure we update the ConnectedClients.
if (logEntry.EventType == EventType.NewConnection)
{
_connectedClientRepository.Insert(new ConnectedClient { LogEntryId = logEntry.LogEntryId });
}

// A (PB) BanKick does _NOT_ register a lost connection .. so we need to make sure we handle those scenario's as a LostConnection.
if (logEntry.EventType == EventType.LostConnection ||
logEntry.EventType == EventType.BanKick)
{
_connectedClientRepository.Delete(logEntry.ClientName, logEntry.ClientIpAndPort);
}

_unitOfWork.Commit();
transactionScope.Complete();
}

现在每个文件都有自己的 UnitOfWork实例(这意味着它有自己的数据库连接、事务和存储库上下文)。所以我假设这意味着有 3 个不同的数据库连接同时发生。

最后,这是使用 Entity Framework作为存储库,但请不要让它阻止您考虑这个问题。

使用分析工具, Isolation LevelSerializable .我也试过 ReadCommitedReadUncommited ,但他们都错误:-
  • ReadCommited : 和上面一样。僵局。
  • ReadUncommited : 不同的错误。 EF 异常表示它希望得到一些结果,但什么也没得到。我猜这是 LogEntryId预期的身份 ( scope_identity ) 值,但由于脏读而无法检索。

  • 请帮忙!

    附注。顺便说一句,它是 Sql Server 2008。

    更新 #2

    阅读后 Remus Rusanu的更新回复,我觉得我可以尝试提供更多信息,看看其他人是否可以提供进一步的帮助。

    EF图

    alt text http://img691.imageshack.us/img691/600/deadlockefmodel.png

    现在,Remus 建议(注意,他确实说他不熟悉 EF)......

    The last piece of the puzzle, the unexplained lock left node has on the PK_ConnectedClients, I will assume is from the EF implementation of InsertOrUpdate. It probably does a lookup first, and because of the FK relationship declared between ConnectedClients and LogEntries, it seeks on PK_ConnectedClients, hence acquiring the serializable lock.



    有趣的。我不知道为什么左边的节点锁定了 PK_ConnectedClients ,如上所述。好的,让我们看看该方法的代码......
    public void InsertOrUpdate(LogEntry logEntry)
    {
    LoggingService.Debug("About to InsertOrUpdate a logEntry");

    logEntry.ThrowIfArgumentIsNull("logEntry");

    if (logEntry.LogEntryId <= 0)
    {
    LoggingService.Debug("Current logEntry instance doesn't have an Id. Instance object will be 'AddObject'.");
    Context.LogEntries.AddObject(logEntry);
    }
    else
    {
    LoggingService.Debug("Current logEntry instance has an Id. Instance object will be 'Attached'.");
    Context.LogEntries.Attach(logEntry);
    }
    }

    唔。这是一个简单的 AddObject (又名。插入)或 Attach (又名。更新)。没有引用。 Sql 代码也没有暗示任何查找内容。

    好吧……我确实有另外两种方法……也许他们正在做一些查找?

    在 ConnectedClientRepository ...
    public void Insert(ConnectedClient connectedClient)
    {
    connectedClient.ThrowIfArgumentIsNull("connectedClient");

    Context.ConnectedClients.AddObject(connectedClient);
    }

    不 -> 也是基本的,如。

    幸运的最后一种方法?哇...现在这很有趣....
    public void Delete(string clientName, string clientIpAndPort)
    {
    clientName.ThrowIfArgumentIsNullOrEmpty("clientName");
    clientIpAndPort.ThrowIfArgumentIsNullOrEmpty("clientIpAndPort");

    // First we need to attach this object to the object manager.
    var existingConnectedClient = (from x in GetConnectedClients()
    where x.LogEntry.ClientName == clientName.Trim() &&
    x.LogEntry.ClientIpAndPort == clientIpAndPort.Trim() &&
    x.LogEntry.EventTypeId == (byte)EventType.NewConnection
    select x)
    .Take(1)
    .SingleOrDefault();

    if (existingConnectedClient != null)
    {
    Context.ConnectedClients.DeleteObject(existingConnectedClient);
    }
    }

    因此,从上面看,我抓取了一个我希望删除的记录实例.. 如果它存在,则将其删除。

    所以..如果我注释掉那个方法调用,以我最初的逻辑方式直到这篇SO帖子的顶部......会发生什么?

    有用。哇哦。

    它也可以用作 SerializableRead Commited - 当我不调用 Delete 时,两者都可以工作方法。

    那么为什么删除方法会获得锁呢? 是因为选择(使用 serializable )锁定并发生了一些死锁?

    read committed ,是否有可能同时发生 3 个删除调用。
  • 1st 抓取数据的一个实例。
  • 第二个(和第三个)抓取相同数据的另一个实例。
  • 现在,第一次删除。美好的。
  • 第二次删除 .. 但该行已经消失了 .. 所以我得到了关于影响意外数量的行 (0) 的奇怪错误。 <== 删除了零个项目。

  • 可能的?如果是这样......呃......我该如何解决这个问题?这是竞争条件的经典案例吗?是否有可能以某种方式防止这种情况发生?

    更新
  • 修复了图像的链接。
  • 链接到原始 XML 死锁文件。这里is the same link .
  • 添加了数据库表架构。
  • 添加了两个表的详细信息。
  • 最佳答案

    左侧节点持有一个 RangeS-U lockPK_CustomerRecords并想要一个 RangeS-U锁定 i1 (我假设它是 LogEntries 上的索引)。右侧节点有一个 RangeS-U锁定 i1并想要一个 RangeI-NPK_CustomerRecords .

    显然死锁发生在 _logEntriesRepository.InsertOrUpdate 之间(左节点)和 _connectedClientRepository.Insert (右节点)。在不知道声明的 EF 关系类型的情况下,我无法评论为什么左侧节点锁定了 PK_CustomerRecords目前它插入 LogEntry .我怀疑这是由 EF 引发的 ORM 类型行为引起的,例如查找“预加载”成员,或者可能是由更高级别的 TransactionScope 引起的,该事务范围围绕发布的代码片段中的范围。

    正如其他人所说,有必要在死锁评估中发布数据库模式,因为访问路径(使用的索引)至关重要。看我的文章Read-Write deadlock有关死锁中索引含义的更详细讨论。

    我的第一个建议是强制交易范围为 read committed .在实践中几乎从不需要默认的 TransactionScopes 可序列化级别,这是一个性能 pig ,在这种特殊情况下,通过将范围锁引入等式,为死锁调查添加了许多不必要的噪音,使一切变得复杂。请把读提交下发生的死锁信息贴出来。

    另外,不要张贴死锁图的图片。一张图千言万语不在这里,贴出原来的死锁XML:它有很多在漂亮的图片中看不到的信息。

    更新

    从死锁 XML 我可以看到左节点正在执行 insert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5) (<executionStack><frame> 元素)。但更重要的是,我可以看到神秘索引“i1”后面的对象:objectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1" .所以死锁发生在 上全文索引 .

    所以死锁的完整解释是:

  • 右节点位于_connectedClientRepository.Insert,它需要PK_ConnectedClients 上的范围插入锁。它在之前执行 _logEntryRepository.InsertOrUpdate 的全文索引 i1 上有一个 RangeS-U 锁。
  • 左节点在_logEntryRepository.InsertOrUpdate,在批处理内的INSERT 语句处,它需要对全文索引i1 的RangeS-U 锁。它在 PK_ConnectedClients 上有一个 RangeS-S 锁来阻止正确的节点,这在 XML 图中没有任何解释。

  • 最后一块拼图,无法解释的锁左节点在 PK_ConnectedClients 上,我假设来自 InsertOrUpdate 的 EF 实现。它可能首先进行查找,并且由于 ConnectedClients 和 LogEntries 之间声明的 FK 关系,它会在 PK_ConnectedClients 上查找,从而获取可序列化锁。

    此处的主要罪魁祸首是事务隔离级别 (Serializable) 和 InsertOrUpdate 上的 EF 行为。我不能就 EF 行为给出建议,但可序列化级别肯定是矫枉过正。这让我们回到了在读取提交级别下遇到的错误,不幸的是,这又是一个我无法评论的 EF 错误。

    关于.net - 为什么这两条sql语句会死锁? (死锁图+详细信息包括在内),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2483294/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com