为什么这两个sql语句会死锁呢? (死锁图+包括详细信息)

发布于 2024-08-26 06:56:30 字数 6752 浏览 10 评论 0原文

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

主死锁图

替代文本 http://img140.imageshack.us/img140/6193/deadlock1 .png 点击此处查看大图。

左侧,详细信息

替代文本 http://img715.imageshack.us/img715/3999/deadlock2.png 点击此处查看大图。

右侧,详细信息

替代文本 http://img686.imageshack.us/img686/5097/deadlock3.png 单击此处查看大图。

原始死锁架构 xml 文件

单击此处下载 xml 文件

表架构

替代文本 http://img509.imageshack.us/img509/5843/deadlockschema。 png

LogEntries 表详细信息

替代文本 http://img28.imageshack.us/ img28/9732/deadlocklogentriestable.png

连接客户端表详细信息

替代文本 http://img11.imageshack.us/img11/7681/deadlockconnectedclient.png" /img11.imageshack.us/img11/7681/deadlockconnectedclient.png

代码在做什么?

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

这是我的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作为存储库,但请不要让这阻止您思考这个问题

使用分析工具,隔离级别Serialized。我还尝试过 ReadCommited 和 ReadUncommited ,但它们都出现错误:-

  • ReadCommited :与上面相同。僵局。
  • ReadUncommited:不同的错误。 EF 异常表示它期望返回一些结果,但什么也没得到。我猜测这是预期的 LogEntryId Identity (scope_identity) 值,但由于脏读而未检索到。

请帮忙!

附言。顺便说一句,这是 Sql Server 2008。


更新 #2

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

EF 图

替代文本 http://img691.imageshack.us/img691/600/deadlockefmodel。现在

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

最后一块拼图, 左节点上有无法解释的锁 PK_ConnectedClients,我假设是 从 EF 实现 插入或更新。它可能会做一个 首先查找,并且由于 FK 之间声明的关系 ConnectedClients 和 LogEntries,它 寻找 PK_ConnectedClients,因此 获取可序列化锁。

有趣的。我不确定为什么左节点锁定了 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);
    }
}

所以,从上面看,我抓住了我想要删除的记录的一个实例..如果它存在,则将其删除。

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

它有效。 WOWZ。

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

那么为什么该删除方法会获得锁定?是因为选择(具有可序列化)会发生锁定和一些死锁吗?

使用已提交的读取,我是否有可能同时发生 3 个删除调用。

  • 第一个获取数据的实例。
  • 第二个(和第三个)获取相同数据的另一个实例。
  • 现在,第一次删除。美好的。
  • 第二次删除..但是该行已经消失了..所以我收到了关于影响了意外数量的行(0)的奇怪错误。 <==删除了零个项目。

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


更新

  • 修复了图像的链接。
  • 原始 XML 死锁文件的链接。这里是相同的链接
  • 添加了数据库表架构。
  • 添加了两个表的详细信息。

I've got the following deadlock graph that describes two sql statements that are deadlocking each other. I'm just not sure how to analyse this problem and then fix up my sql code to prevent this from happening.

Main deadlock graph

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

Left side, details

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

Right side, details

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

Raw Deadlock Schema xml file

Click here to download the xml file.

Table schema

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

LogEntries Table details

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

Connected Clients Table details

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

What is the code doing?

I'm reading in a number of files (eg. lets say 3, for this example) at the same time. Each file contains different data BUT the same type of data. I then insert the data into LogEntries table and then (if required) I insert or delete something from the ConnectedClients table.

Here's my sql code.

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();
}

Now each file has it's own UnitOfWork instance (which means it has it's own database connection, transaction and repository context). So i'm assuming this means there's 3 different connections to the db all happening at the same time.

Finally, this is using Entity Framework as the repository, but please don't let that stop you from having a think about this problem.

Using a profiling tool, the Isolation Level is Serializable. I've also tried ReadCommited and ReadUncommited, but they both error :-

  • ReadCommited: same as above. Deadlock.
  • ReadUncommited: different error. EF exception that says it expected some result back, but got nothing. I'm guessing this is the LogEntryId Identity (scope_identity) value that is expected but not retrieve because of the dirty read.

Please help!

PS. It's Sql Server 2008, btw.


Update #2

After reading Remus Rusanu's updated reply, I felt that I could try and provide a bit more information to see if someone else can further help.

EF Diagram

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

Now, Remus suggests (and note, he does say he's unfamiliar with 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.

Interesting. I'm not sure why the left node has a lock on PK_ConnectedClients, as suggested above. Ok, lets check out the code for that method....

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);
    }
}

Hmm. it's a simple AddObject (aka. Insert) or Attach (aka. Update). No references. Sql code also doesn't hint of any lookup stuff.

Ok then ... i do have two other methods ... maybe they are doing some lookups?

In ConnectedClientRepository ...

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

    Context.ConnectedClients.AddObject(connectedClient);
}

Nope -> also basic, as.

Lucky last method? Wow .. now this is interesting....

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, looking above, i grab an instance of the record i wish to delete .. and if it exists, then delete it.

So .. if i comment out that method call, in my initial logic way up to the top of this SO post ... what happens?

it works. WOWZ.

It also works as either Serializable or Read Commited - both work when i don't call the Delete method.

So why would that delete method be getting a lock? Is it becuase the select (with serializable) does a lock and some deadlock happens?

With read committed, is it possible that i have 3 calls to the delete happen at the same time.

  • 1st grabs an instance of the data.
  • 2nd (and 3rd) grabs another instance of the same data.
  • Now, 1st delete's. fine.
  • 2nd deletes .. but the row has gone .. so therefore i get that weird error about affected an unexpected number of rows (0). <== zero items deleted.

Possible? If so .. er ... how can I fix this? Is this a classic case of a race condition? Is it possible to prevent this from happeneing somehow?


Updates

  • Fixed the links to the images.
  • Link to the raw XML deadlock file. Here is the same link.
  • Added database table schema.
  • Added both table details.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

尘世孤行 2024-09-02 06:56:30

左侧节点在 PK_CustomerRecords 上持有 RangeS-U 锁,并希望在 i1 上获得 RangeS-U 锁(我假设它是 LogEntries 上的索引)。右侧节点在 i1 上有一个 RangeS-U 锁,并希望在 PK_CustomerRecords 上有一个 RangeI-N 锁。

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

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

我的第一个建议是强制事务范围读取已提交。 TransactionScopes 的默认可序列化级别在实践中几乎从不需要,它会消耗性能,并且在这种特殊情况下,通过将范围锁带入等式中,会为死锁调查带来大量不必要的噪音,从而使一切变得复杂。请贴出read commited下发生的死锁信息。

另外,不要发布死锁图的图片。一图胜千言在这里是不正确的,发布原始的僵局XML:它有很多在漂亮的图片中看不到的信息。

更新

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

所以死锁的完整解释是:

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

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

这里的罪魁祸首是事务隔离级别(可序列化)和 InsertOrUpdate 上的 EF 行为。我无法就 EF 行为提供建议,但可序列化级别肯定有点过分了。这让我们回到了在已提交读级别下遇到的错误,不幸的是,这又是一个我无法评论的 EF 错误。

The left side node is holding an RangeS-U lock on PK_CustomerRecords and wants an RangeS-U lock on i1 (I assume its an index on LogEntries). The right side node has an RangeS-U lock on i1 and wants an RangeI-N on PK_CustomerRecords.

Apparently the deadlock occurs between the _logEntriesRepository.InsertOrUpdate (left node) and _connectedClientRepository.Insert (right node). Without knowing the type of EF relations declared, I can't comment on why does the left side node have a lock on PK_CustomerRecords at the moment it inserts the LogEntry. I suspect this is caused by either a ORM type behavior induced by EF, like lookup of a 'preloaded' member, or it may be caused by a higher level TransactionScope that surrounds the scope in the code snipped posted.

As other have said, it is necessary to post the database schema in a deadlock evaluation, because the access path (indexes used) is critical. See my article Read-Write deadlock for a more detailed discussion on the implication of indexes in deadlocks.

My first recommendation would be to force the transaction scope to be read committed. The default serializable level of TransactionScopes is almost never needed in practice, is a performance hog, and in this particular case ads a lot of unnecessary noise to the deadlock investigation by bringing the range locks into the equation, complicating everything. Please post the deadlock information that occurs under read committed.

Also, don't post a picture of the deadlock graph. A picture tells a thousand words is not true here, post the original deadlock XML: it has a lot of information not visible in the pretty pictures.

Update

From the deadlock XML I can see that the left node is executing insert [dbo].[LogEntries]([GameFileId], [CreatedOn], [EventTypeId], [Message], [Code], [Violation], [ClientName], [ClientGuid], [ClientIpAndPort]) values (@0, @1, @2, null, null, null, @3, @4, @5) (the <executionStack><frame> element). But more importantly, I can see the object behind the misterious index 'i1': objectname="AWing.sys.fulltext_index_docidstatus_1755869322" indexname="i1". So the deadlock occurs on a fulltext index.

So the full explanation of deadlock is:

  • right node is at _connectedClientRepository.Insert, it needs an range insert lock on PK_ConnectedClients. It has an RangeS-U lock on the fulltext index i1 from previously executing _logEntryRepository.InsertOrUpdate.
  • left node is at the _logEntryRepository.InsertOrUpdate, at the INSERT statement inside the batch, and it needs a RangeS-U lock on the fulltext index i1. It has a RangeS-S lock on PK_ConnectedClients that blocks the right node, and this is not explained by anything in the graph XML.

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.

The primary culprit here are the transaction isolation level (Serializable) and the EF behavior on InsertOrUpdate. I can't give advice on the EF behavior, but the serializable level is overkill for sure. Which takes us back to the error you get under read committed level, which, unfortunately, is again an EF error I can't comment on.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文