我的大型网络应用程序中出现了很多死锁。
如何自动重新运行死锁的事务? (ASP.NET MVC/SQL Server)
在这里,我想重新运行死锁事务,但我被告知要摆脱死锁 - 这比尝试捕获死锁要好得多。
所以我花了一整天的时间使用 SQL Profiler,设置跟踪键等。这就是我得到的。
有一张
Users
桌子。我有一个非常高的可用页面,其中包含以下查询(这不是唯一的查询,但它是导致麻烦的查询)
UPDATE Users
SET views = views + 1
WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID)
然后在ALL页面中有以下查询:
User = DB.Users.SingleOrDefault(u => u.Password == password && u.Name == username);
这就是我从 cookie 获取用户的地方。
经常发生死锁,并且第二个 Linq-to-SQL 查询被选为受害者,因此它不会运行,并且我的网站的用户会看到错误屏幕。
这是来自 SQL Profiler 捕获的 .XDL 图表的信息(这只是第一个死锁,不是唯一的死锁。整个列表非常庞大。):
<deadlock-list>
<deadlock victim="process824df048">
<process-list>
<process id="process824df048" taskpriority="0" logused="0" waitresource="PAGE: 7:1:13921" waittime="1830" ownerId="91418" transactionname="SELECT" lasttranstarted="2010-05-31T12:17:37.663" XDES="0x868175e0" lockMode="S" schedulerid="2" kpid="5076" status="suspended" spid="72" sbid="0" ecid="2" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2">
*password------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
</inputbuf>
</process>
<process id="process8765fb88" taskpriority="0" logused="216" waitresource="PAGE: 7:1:14196" waittime="1822" ownerId="91408" transactionname="UPDATE" lasttranstarted="2010-05-31T12:17:37.640" XDES="0x86978e90" lockMode="IX" schedulerid="2" kpid="5216" status="suspended" spid="73" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2010-05-31T12:17:37.557" lastbatchcompleted="2010-05-31T12:17:37.557" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91408" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="database.dbo.UpdateUserStats" line="31" stmtstart="1794" stmtend="2088" sqlhandle="0x03000700bac8836333e58f00879d00000100000000000000">
UPDATE Users
SET Views = Views + 1
WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID) </frame>
<frame procname="adhoc" line="1" stmtstart="84" sqlhandle="0x01000700b7c78e0760dd3f81000000000000000000000000">
EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0 </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@p0 int,@RETURN_VALUE int output)EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0 </inputbuf>
</process>
<process id="process86ce0988" taskpriority="0" logused="10000" waittime="1806" schedulerid="1" kpid="2604" status="suspended" spid="72" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2">
*password------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- </inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="13921" dbid="7" objectname="database.dbo.Users" id="lock85535c80" mode="IX" associatedObjectId="72057594046382080">
<owner-list>
<owner id="process8765fb88" mode="IX"/>
</owner-list>
<waiter-list>
<waiter id="process824df048" mode="S" requestType="wait"/>
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="14196" dbid="7" objectname="database.dbo.Users" id="lock8469f980" mode="SIU" associatedObjectId="72057594046382080">
<owner-list>
<owner id="process86ce0988" mode="S"/>
</owner-list>
<waiter-list>
<waiter id="process8765fb88" mode="IX" requestType="convert"/>
</waiter-list>
</pagelock>
<exchangeEvent id="Pipe894b0680" WaitType="e_waitPipeGetRow" nodeId="0">
<owner-list>
<owner id="process824df048"/>
</owner-list>
<waiter-list>
<waiter id="process86ce0988"/>
</waiter-list>
</exchangeEvent>
</resource-list>
</deadlock>
我读了很多关于死锁的内容......但我不明白为什么这会导致死锁。
显然这两个查询都经常运行。至少每秒一次。也许更频繁(300-400 个用户在线)。那么它们可以很容易地同时运行,但是为什么会导致死锁呢?请帮忙。
谢谢你
您需要捕获死锁图。连接 Profiler 并捕获 Deadlock Graph Event 类。保存 .XDL 图表并将该信息添加到您的帖子中。
在那之前,很明显您的 DB.Users.SingleOrDefault 查询至少需要名称上的索引,如果不需要名称和密码的话:
CREATE INDEX idxUsersNamePassword on Users(Name,Password);
我希望 Users 已经有一个关于 ID 的索引,并且 Articles 有一个关于 ArticleID 的索引,其中也涵盖了 AuthorID。假设 Users.ID 和 Articles.ArticleID 是它们各自表中的 PK,它们可能是各自的聚集键,所以确实如此。不过,值得仔细检查。
并且,由于我已经在上一篇文章中回答过您一次,您决定继续前进并保持未答复,因此您应该考虑打开快照隔离:
ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON
除此之外,以明文形式存储密码是一个主要的#失败。
死锁信息后更新
共有三个进程(请求):
SELECT ... FROM Users WHERE Password = ... and Name = ...
SELECT ... FROM Users WHERE Password = ... and Name = ...
UPDATE ...
死锁循环为:
因此循环是C->A->B->C。
从所涉及的两个 SELECT 决定 1)使用并行计划和 2)使用页锁的事实来看,很明显它们对整个 Users 表进行了端到端扫描。所以问题是,正如我所预测的,用户的(名称,密码)上缺少索引,这会导致查询扫描太多数据。添加索引会将 SELECT 变成对 Nc 索引的直接 SEEK 和对 Clustered 索引的查找,这将大大减少与 UPDATE 重叠的窗口。现在 UPDATE 几乎肯定会与all SELECT 发生冲突,因为每个 SELECT 都保证读取每一行。
添加索引将缓解眼前的问题。使用快照隔离将“掩盖”问题,因为除非添加(名称,密码)索引,否则端到端扫描仍然会发生。或者只有(姓名)也可能有效。 为了未来的可扩展性,在每个页面视图上更新“视图”列
将不起作用。延迟更新、批量聚合计数更新、对Users表进行垂直分区以及取出Views列都是可行的替代方案。您的问题与此处的问题有很多相似之处
在这种情况下(即您正在读取的数据类型以及该数据上发生的更新的性质),我将在读取未提交隔离时运行用户查找查询。