我有以下死锁图,它描述了两个互相死锁的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.
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
表中插入或删除一些内容。
这是我的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
实例(这意味着它拥有自己的数据库连接,事务和存储库上下文)。所以我假设这意味着有三个不同的数据库连接同时发生。
最后,这是使用Entity Framework
作为存储库,但请不要让它阻止您对此问题进行思考。
使用分析工具,Isolation Level
为Serializable
。我还尝试了ReadCommited
和ReadUncommited
,但他们都错了: -
ReadCommited
:与上述相同。死锁。ReadUncommited
:不同的错误。 EF异常表示它预计会有一些结果,但什么都没有。我猜这是预期的LogEntryId
Identity (scope_identity
)值,但由于脏读而无法检索。请帮忙!
PS。这是Sql Server 2008,顺便说一句。
在阅读Remus Rusanu的更新回复后,我觉得我可以尝试提供更多信息,看看其他人是否可以提供更多帮助。
alt text http://img691.imageshack.us/img691/600/deadlockefmodel.png
现在,Remus建议(注意,他确实说他不熟悉EF)......
最后一块拼图, 原因不明的锁左节点就有了 PK_ConnectedClients,我将假设是 来自EF的实施 InsertOrUpdate。它可能会做一个 首先查找,因为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帖子的顶部......会发生什么?
它有效。 WOWZ
它也适用于Serializable
或Read Commited
- 当我不调用Delete
方法时,它们都有效。
那么为什么删除方法会被锁定?是否因为select(serializable
}会发生锁定并发生一些死锁?
使用read committed
,我是否有可能同时发生3次删除调用。
可能?如果是这样..呃......我该怎么解决这个问题?这是一个竞争条件的经典案例吗?有可能以某种方式防止这种情况发生吗?
答案 0 :(得分:4)
左侧节点在RangeS-U lock
上持有PK_CustomerRecords
并希望在RangeS-U
上锁定i1
(我假设它是LogEntries
上的索引)。右侧节点在RangeS-U
上有一个i1
锁,并希望RangeI-N
上有PK_CustomerRecords
。
显然,_logEntriesRepository.InsertOrUpdate
(左侧节点)和_connectedClientRepository.Insert
(右侧节点)之间发生死锁。在不知道声明的EF关系类型的情况下,我无法评论为什么左侧节点在插入PK_CustomerRecords
时锁定LogEntry
。我怀疑这是由EF引起的ORM类型行为引起的,比如查找“预加载”成员,或者它可能是由更高级别的TransactionScope引起的,它围绕着被发布的代码中的范围。
正如其他人所说,有必要在死锁评估中发布数据库模式,因为访问路径(使用的索引)很关键。有关索引在死锁中的含义的更详细讨论,请参阅我的文章Read-Write deadlock。
我的第一个建议是强制交易范围为read committed
。在实践中几乎从不需要TransactionScopes的默认可序列化级别,这是一种性能损失,并且在这种特殊情况下,通过将范围锁定纳入等式,使死锁调查中出现了许多不必要的噪声,使一切变得复杂。请发布在read committed下发生的死锁信息。
另外,不要发布死锁图的图片。一张图片告诉千言万语不对,发布原始死锁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"
。因此,死锁发生在全文索引。
所以死锁的完整解释是:
最后一块拼图,无法解释的锁左节点在PK_ConnectedClients上,我假设是来自InsertOrUpdate的EF实现。它可能首先进行查找,并且由于在ConnectedClients和LogEntries之间声明了FK关系,它会在PK_ConnectedClients上进行搜索,从而获取可序列化的锁。
这里的主要元凶是事务隔离级别(Serializable)和InsertOrUpdate上的EF行为。我无法就EF行为给出建议,但可序列化级别肯定是过度杀伤。这让我们回到你在读取提交级别下得到的错误,不幸的是,这也是我无法评论的EF错误。