7

I am receiving a lot of deadlocks in my big web application.

How to automatically re-run deadlocked transaction? (ASP.NET MVC/SQL Server)

Here I wanted to re-run deadlocked transactions, but I was told to get rid of the deadlocks - it's much better, than trying to catch the deadlocks.

So I spent the whole day with SQL Profiler, setting the tracing keys etc. And this is what I got.

There's a Users table. I have a very high usable page with the following query (it's not the only query, but it's the one that causes troubles)

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

And then there's the following query in ALL pages:

User = DB.Users.SingleOrDefault(u => u.Password == password && u.Name == username);

That's where I get User from cookies.

Very often a deadlock occurs and this second Linq-to-SQL query is chosen as a victim, so it's not run, and users of my site see an error screen.

This is information from the .XDL graph captured by SQL Profiler (It's just the first deadlock, it's not the only one. The whole list is gigantic.):

<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>

I read a lot about deadlocks... And I don't understand why this is causing a deadlock.

So obviously both of this queries run very often. At least once a second. Maybe even more often (300-400 users online). So they can be run at the same time very easily, but why does it cause a deadlock? Please help.

Thank you

4

3 に答える 3

12

デッドロックグラフをキャプチャする必要があります。プロファイラーを接続し、デッドロックグラフイベントクラスをキャプチャします。.XDLグラフを保存し、その情報を投稿に追加します。

それまでは、DB.Users.SingleOrDefaultクエリで、名前とパスワードではなくても、少なくとも名前にインデックスが必要であることは明らかです。

CREATE INDEX idxUsersNamePassword on Users(Name,Password);

ユーザーにはすでにIDのインデックスがあり、ArticlesにはAuthorIDもカバーするArticleIDのインデックスがあると思います。Users.IDとArticles.ArticleIDがそれぞれのテーブルのPKであると仮定すると、それらはおそらくそれぞれのクラスター化されたキーであるため、それは真実です。ただし、再確認する価値はあります。

また、前回の投稿ですでに一度回答したので、先に進んで未回答のままにすることにしたので、スナップショットアイソレーションをオンにすることを検討する必要があります。

ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

それに加えて、パスワードをクリアテキストで保存することは大きな#failです。

デッドロック情報の後に更新

3つのプロセス(リクエスト)があります。

  • A)...実行中のF048SELECT ... FROM Users WHERE Password = ... and Name = ...
  • B)...実行中の0988SELECT ... FROM Users WHERE Password = ... and Name = ...
  • C)...実行中のFB88UPDATE ...

デッドロックサイクルは次のとおりです。

  1. CはページIXロックを待機し、AのSロックによってブロックされます
  2. BはページSロックを待機し、CのIXロックによってブロックされます
  3. 並列交換リソースを待機し、Bによってブロックされます

したがって、サイクルはC-> A->B->Cです。

関連する2つのSELECTが、1)並列プランを使用し、2)ページロックを使用することを決定したという事実から、Usersテーブル全体のエンドツーエンドスキャンを実行することは明らかです。したがって、問題は、私が予測したように、ユーザーの(名前、パスワード)のインデックスが不足しているため、クエリが大量のデータをスキャンすることです。インデックスを追加すると、SELECTがNcインデックスでのストレートSEEKになり、クラスター化インデックスでのルックアップになります。これにより、UPDATEとのオーバーラップのウィンドウが大幅に減少します。現在、すべてのSELECTはすべての行を読み取ることが保証されているため、UPDATEはすべてのSELECTと競合することがほぼ保証されています。

インデックスを追加すると、当面の問題が軽減されます。(名前、パスワード)インデックスが追加されない限り、エンドツーエンドスキャンが引き続き発生するため、スナップショットアイソレーションを使用すると問題がマスクされます。または、(名前)だけでも機能する可能性があります。

将来のスケーラビリティのために、すべてのページビューの[ビュー]列を更新することはできません。遅延更新、バッチ集計カウント更新、Usersテーブルの垂直分割、およびViews列の削除は実行可能な代替手段です。

于 2010-05-31T19:07:11.793 に答える
1

この状況(つまり、読み取っているデータの種類とそのデータで発生する更新の性質)では、読み取りのコミットされていない分離でユーザールックアップクエリを実行します。

または、より複雑な変更。あなたが投稿した説明から、ユーザーレコードの視聴回数を維持しないことを検討します。代わりに、Articleに対してViewCountを記録し、AuthorIDによるArticles.ViewCountの合計からユーザーの合計ビューを導き出します。

于 2010-05-31T18:52:47.327 に答える
1

あなたの問題は、ここでの問題と多くの類似点がありますSQLServer2005でのデッドロックの診断

(Linq to SQL、読み取り専用トランザクションが読み取り/書き込みトランザクションによってデッドロックされています)

SQL2005以降を使用している場合は、そのスレッドで説明されているようにスナップショットアイソレーションを設定することで問題を解決できます。それ以外の場合は、使用しているバージョンの詳細で投稿を更新してください。

于 2010-05-31T18:09:35.590 に答える