はい、それは非常に一般的な問題のように聞こえますが、私はまだそれをあまり絞り込むことができませんでした。
したがって、SQLバッチファイルにUPDATEステートメントがあります。
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
Bには40kレコード、Aには4Mレコードがあり、A.B_IDを介して1対nに関連付けられていますが、2つの間にFKはありません。
したがって、基本的にはデータマイニングの目的でフィールドを事前に計算しています。この質問のためにテーブルの名前を変更しましたが、ステートメントは変更しませんでした。それは本当に簡単です。
実行には数時間かかるため、すべてをキャンセルすることにしました。DBが破損したので、それを削除し、ステートメントを実行する直前に行ったバックアップを復元し、カーソルで詳細に進むことにしました。
DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
RAISERROR(@Msg, 10, 1) WITH NOWAIT
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = @Id
FETCH NEXT FROM CursorB INTO @Id
END
これで、IDが降順のメッセージで実行されていることがわかります。何が起こるかは、id = 40kからid = 13になるまで約5分かかります
そして、id 13では、何らかの理由で、ハングしているようです。DBにはSSMS以外の接続はありませんが、実際にはハングしていません。
- ハードドライブは継続的に実行されているため、確実に何かを実行しています(Process Explorerで、実際にそれを使用しているsqlserver.exeプロセスであることを確認しました)
私はsp_who2を実行し、SUSPENDEDセッションのSPID(70)を見つけて、次のスクリプトを実行しました。
select * from sys.dm_exec_requests r join sys.dm_os_tasks t on r.session_id = t.session_id where r.session_id = 70
これにより、ほとんどの場合、PAGEIOLATCH_SHであるwait_typeが得られますが、実際にはWRITE_COMPLETIONに変更されることがあります。これは、ログをフラッシュしているときに発生すると思います
- DBを復元したとき(およびIDが13になったとき)のログファイルは1.6GBでしたが、現在は3.5GBです。
その他の役立つ情報:
- B_ID 13のテーブルAのレコード数は大きくありません(14)
- 私の同僚は彼女のマシンで同じ問題を抱えておらず、同じ構造を持つこのDBのコピー(数か月前から)を持っています。
- テーブルAは、DBで最大のテーブルです。
- いくつかのインデックスがあり、いくつかのインデックス付きビューがそれを使用します。
- DBには他のユーザーはいません。ローカルであり、アプリケーションはそれを使用していません。
- LDFファイルのサイズに制限はありません。
- 復旧モデルはシンプル、互換性レベルは100
- Procmonは多くの情報を提供しません。sqlserver.exeはMDFファイルとLDFファイルから多くの読み取りと書き込みを行っています。
私はまだそれが完了するのを待っています(それは1時間30分です)が、誰かが私にこれをトラブルシューティングできる他のアクションを提供することを望んでいました。
編集:procmonログからの抽出の追加
15:24:02.0506105 sqlservr.exe 1760 ReadFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal
DBCC PAGEを使用すると、テーブルA(またはそのインデックスの1つ)のように見えるフィールドからの読み取りと書き込みのように見えますが、異なるB_IDの場合は13.インデックスを再構築しますか?
編集2:実行計画
そのため、クエリをキャンセルし(実際にはDBとそのファイルを削除してから復元しました)、実行プランを確認しました。
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13
(推定)実行プランは、どのB.IDの場合も同じで、かなり簡単に見えます。WHERE句はBの非クラスター化インデックスでインデックスシークを使用し、JOINはテーブルの両方のPKでクラスター化インデックスシークを使用します。Aのクラスター化インデックスシークは並列処理(x7)を使用し、CPU時間の90%を表します。
さらに重要なことに、ID 13のクエリを実際に実行するとすぐに実行されます。
編集3:インデックスの断片化
インデックスの構造は次のとおりです。
Bには1つのクラスター化PK(IDフィールドではない)と1つの非クラスター化一意インデックスがあり、最初のフィールドはB.IDです。この2番目のインデックスは常に使用されるようです。
Aには1つのクラスター化PKがあります(フィールドは関連していません)。
Aには7つのビュー(すべてAXフィールドを含む)があり、それぞれに独自のクラスター化PKがあり、もう1つのインデックスにはAXフィールドも含まれています。
ビューは(この方程式にないフィールドで)フィルター処理されているので、UPDATE Aがビュー自体を使用する方法はないと思います。しかし、それらにはAXを含むインデックスがあるので、AXを変更すると、フィールドを含む7つのビューと7つのインデックスが書き込まれます。
このため、UPDATEは遅くなることが予想されますが、特定のIDが他のIDよりもはるかに長くなる理由はありません。
すべてのインデックスの断片化をチェックしました。ビューのセカンダリインデックスを除いて、すべて0.1%未満でした。すべて25%から50%です。すべてのインデックスのFILL FACTORは、90%から95%の間で問題ないようです。
すべてのセカンダリインデックスを再編成し、スクリプトを再実行しました。
それはまだ絞首刑にされていますが、別の時点で:
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
以前は、メッセージログは次のようになりました。
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
Updating A for B_ID=13
これは奇妙なことです。なぜなら、WHILE
ループ内の同じポイントでハングすることさえないからです。残りは同じように見えます。sp_who2で待機している同じUPDATE行、同じPAGEIOLATCH_EX待機タイプ、およびsqlserver.exeからの同じ重いHDの使用。
次のステップは、すべてのインデックスとビューを削除して、それらを再作成することです。
編集4:インデックスを削除してから再構築する
そのため、テーブルにあるインデックス付きビューをすべて削除しました(そのうちの7つ、クラスター化されたビューを含め、ビューごとに2つのインデックス)。最初のスクリプトを(カーソルなしで)実行しましたが、実際には5分で実行されました。
したがって、私の問題はこれらのインデックスの存在に起因します。
更新を実行した後、インデックスを再作成しましたが、16分かかりました。
インデックスの再構築に時間がかかることを理解しました。実際、タスク全体で20分で問題ありません。
それでもわからないのは、インデックスを削除せずに更新を実行すると数時間かかるのに、最初に削除してから再作成すると20分かかる理由です。どちらにしても、同じくらいの時間がかかりませんか?
DBCC PAGE
して何が書き込まれているかを確認できます。