我有以下死锁图,它描述了两个相互死锁的 sql 语句。我只是不确定如何分析这个问题,然后修复我的 sql 代码以防止这种情况发生。
主要死锁图
alt text http://img140.imageshack.us/img140/6193/deadlock1.png 点击这里查看大图。
左侧,细节
alt text http://img715.imageshack.us/img715/3999/deadlock2.png 点击这里查看大图。
右侧,细节
alt text http://img686.imageshack.us/img686/5097/deadlock3.png 点击这里查看大图。
原始死锁模式 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
代码在做什么?
我正在同时读取多个文件(例如,对于这个例子,我们说 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
用作存储库,但请不要让这阻止您考虑这个问题。
使用分析工具,Isolation Level
是Serializable
. 我也试过ReadCommited
and ReadUncommited
,但他们都错误: -
ReadCommited
: 同上。僵局。ReadUncommited
: 不同的错误。EF 异常表示它期望返回一些结果,但一无所获。我猜这是预期的LogEntryId
Identity (scope_identity
) 值,但由于脏读而无法检索。
请帮忙!
PS。顺便说一句,这是 Sql Server 2008。
更新#2
在阅读了Remus Rusanu的更新回复后,我觉得我可以尝试提供更多信息,看看其他人是否可以提供进一步的帮助。
EF图
替代文字 http://img691.imageshack.us/img691/600/deadlockefmodel.png
现在,Remus 建议(并注意,他确实说他不熟悉 EF)...
谜题的最后一块,PK_ConnectedClients 上的无法解释的左节点锁定,我将假设来自 InsertOrUpdate 的 EF 实现。它可能首先进行查找,并且由于在 ConnectedClients 和 LogEntries 之间声明的 FK 关系,它会在 PK_ConnectedClients 上查找,从而获取可序列化锁。
有趣的。如上所述,我不确定为什么左节点有一个 lock on 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帖子的顶部......会发生什么?
有用。哇。
当我不调用该方法时,它也可以作为一个Serializable
或两个工作。Read Commited
Delete
那么为什么删除方法会被锁定呢?是因为 select (with serializable
) 做了一个锁并且发生了一些死锁吗?
有了read committed
,我是否有可能同时发生 3 个删除调用。
- 第一个抓取数据的一个实例。
- 第二个(和第三个)抓取相同数据的另一个实例。
- 现在,第一次删除。美好的。
- 第二次删除..但是该行已经消失了..因此我得到了一个奇怪的错误,即影响了意外的行数(0)。<== 零个项目被删除。
可能的?如果是这样......呃......我该如何解决这个问题?这是竞争条件的经典案例吗?是否有可能以某种方式防止这种情况发生?
更新
- 修复了图片的链接。
- 链接到原始 XML 死锁文件。这是相同的链接。
- 添加了数据库表模式。
- 添加了两个表的详细信息。