【发布时间】:2010-03-20 14:00:06
【问题描述】:
我有以下死锁图,它描述了两个相互死锁的 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
LogEntries 表详细信息
alt text http://img28.imageshack.us/img28/9732/deadlocklogentriestable.png
连接的客户表详细信息
alt text 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 和ReadUncommited,但它们都出错了:-
-
ReadCommited:同上。死锁。 -
ReadUncommited:不同的错误。 EF 异常表示它期望返回一些结果,但一无所获。我猜这是LogEntryIdIdentity (scope_identity) 值,由于脏读而无法检索到。
请帮忙!
PS。顺便说一句,这是 Sql Server 2008。
更新 #2
在阅读Remus Rusanu的更新回复后,我觉得我可以尝试提供更多信息,看看其他人是否可以提供进一步的帮助。
EF图
alt text http://img691.imageshack.us/img691/600/deadlockefmodel.png
现在,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);
}
}
所以,看上面,我抓住了我想删除的记录的一个实例......如果它存在,那么删除它。
所以..如果我注释掉那个方法调用,在我最初的逻辑方式中直到这篇 SO 帖子的顶部......会发生什么?
有效。哇。
它也可以作为Serializable 或Read Commited 工作——当我不调用Delete 方法时两者都工作。
那么为什么删除方法会被锁定?是因为选择(使用serializable)会锁定并发生一些死锁吗?
对于read committed,我是否有可能同时发生 3 个删除调用。
- 第一次抓取数据的一个实例。
- 第二个(和第三个)抓取相同数据的另一个实例。
- 现在,第一次删除。很好。
- 第二次删除 .. 但该行已经消失了 .. 所以我收到了关于影响意外行数 (0) 的奇怪错误。
可能吗?如果是这样......呃......我该如何解决这个问题?这是竞争条件的经典案例吗?是否有可能以某种方式防止这种情况发生?
更新
- 修复了图片的链接。
- 链接到原始 XML 死锁文件。这里is the same link。
- 添加了数据库表架构。
- 添加了两个表的详细信息。
【问题讨论】:
-
_unitOfWork.Commit() 做了什么; ?
-
大图的链接似乎搞砸了。最后一个实际上是第二个的版本。第二个是第一个的副本。
-
在FK上有索引的两个表之间有外键关系吗?
-
能把执行的sql语句和数据库schema贴一下吗?
-
@STMax :它们列在第二张和第三张图片中。够了吗?
标签: .net sql-server transactions deadlock unit-of-work