7

我在我的大型 Web 应用程序中收到很多死锁。

如何自动重新运行死锁事务?(ASP.NET MVC/SQL 服务器)

在这里,我想重新运行死锁的事务,但有人告诉我要摆脱死锁——这比试图抓住死锁要好得多。

所以我花了一整天的时间在 SQL Profiler 上,设置跟踪键等。这就是我得到的。

有一张Users桌子。我有一个非常高的可用页面,其中包含以下查询(它不是唯一的查询,但它是导致麻烦的那个)

UPDATE Users
SET views = views + 1
WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID)

然后在所有页面中都有以下查询:

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 个在线用户)。所以它们可以很容易地同时运行,但是为什么会导致死锁呢?请帮忙。

谢谢

4

3 回答 3

12

您需要捕获死锁图。附加 Profiler 并捕获死锁图事件类。保存 .XDL 图表并将该信息添加到您的帖子中。

在那之前,很明显您的 DB.Users.SingleOrDefault 查询至少需要 Name 上的索引,如果不是 Name 和 Password 上的话:

CREATE INDEX idxUsersNamePassword on Users(Name,Password);

我希望用户已经有一个关于 ID 的索引,并且文章有一个关于 ArticleID 的索引,它也涵盖了 AuthorID。假设 Users.ID 和 Articles.ArticleID 是它们各自表中的 PK,它们可能是各自的聚集键,所以这是真的。不过,值得仔细检查。

而且,正如我在上一篇文章中已经回答过你一次,你决定继续前进并没有回答,你应该考虑打开Snapshot Isolation

ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

除此之外,以明文形式存储密码是一个主要的#fail。

死锁信息后更新

共有三个进程(请求):

  • A) ...F048 正在运行SELECT ... FROM Users WHERE Password = ... and Name = ...
  • B) ...0988 正在运行SELECT ... FROM Users WHERE Password = ... and Name = ...
  • C) ...FB88 正在运行UPDATE ...

死锁循环为:

  1. C 等待 Page IX 锁,被 A 的 S 锁阻塞
  2. B 等待 Page S 锁,被 C 的 IX 锁阻塞
  3. A 等待并行交换资源,被 B 阻塞

因此,循环是 C->A->B->C。

从所涉及的两个 SELECT 决定 1) 使用并行计划和 2) 使用页锁这一事实可以看出,它们对整个用户表进行端到端扫描。所以问题是,正如我预测的那样,用户的(名称,密码)缺乏索引,这导致查询扫描太多数据。添加索引会将 SELECT 变成 Nc 索引上的直接 SEEK 和 Clustered 索引上的查找,这将大大减少与 UPDATE 重叠的窗口。现在,几乎可以保证 UPDATE 与所有SELECT 冲突,因为每个 SELECT 都保证读取每一行。

添加索引将缓解眼前的问题。使用快照隔离将掩盖问题,因为除非添加(名称,密码)索引,否则仍然会发生端到端扫描。或者只有 (Name) 也可能起作用。

对于未来的可扩展性,更新每个页面视图上的视图列将不起作用。延迟更新、批量聚合计数更新、垂直分区用户表和取出视图列是可行的替代方案。

于 2010-05-31T19:07:11.793 回答
1

您的问题与这里的Diagnosing Deadlocks in SQL Server 2005有很多相似之处

(Linq to SQL,只读事务被读写事务死锁)

如果您使用的是 SQL2005 或更高版本,那么在该线程上讨论的设置快照隔离可能会完成这项工作。否则,请使用您正在使用的版本的详细信息更新您的帖子。

于 2010-05-31T18:09:35.590 回答
1

在这种情况下(即您正在读取的数据类型以及该数据上发生的更新的性质),我将在未提交的读取隔离下运行用户查找查询。

或者,更复杂的变化。根据您发布的描述,我会考虑不维护用户记录的视图计数。相反,我会根据文章记录 ViewCount,然后从 AuthorID 的 Articles.ViewCount 总和中得出用户的总视图。

于 2010-05-31T18:52:47.327 回答