SQL更新の処理に非常に長い時間がかかる/ディスク使用率が高い


8

はい、それは非常に一般的な問題のように聞こえますが、私はまだそれをあまり絞り込むことができませんでした。

したがって、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分かかる理由です。どちらにしても、同じくらいの時間がかかりませんか?


1
SQL Serverエラーログに何かありますか?また、procmonから、それが書き込んでいるファイルのオフセットは何ですか?8,192で割ってページを取得し、を使用DBCC PAGEして何が書き込まれているかを確認できます。
マーティン・スミス

3.5GBは、32ビットのWindowsワークステーションが処理できるRAMの最大量のように見えます。
tschmit007 2013年

私はSSMS SQL Serverのログと何もいずれかのWindowsイベントログの中に復元されたので、絶対に何もありません@MartinSmith
GFK

テーブルAのインデックスはどのようになっていますか(どの列など)?それらは断片化されていますか?
スチュアートエインズワース2013年

@ tschmit007 Win Server 2008 R2 x64上のSQL 2008 R2 x64 Dev Edition。これは、Hyper-Vで自身を実行するVMです(ホストも2008 R2 x64です)。VMには5 GBのうち4.2 GBの物理メモリが使用され、最大10 GBのうち4.6 GBがコミットされます。ホストには、8 GBのうち7.2 GBの物理メモリが使用され、最大16 GBのうち7.8コミットされます。両方のマシンは、HDの使用により遅くなりますが、目詰まりしません。
GFK、2013年

回答:


0
  1. UPDATEコマンドを使います。CURSORは、実行しようとしている処理が遅くなります。
  2. インデックス付きビューのインデックスを含むすべてのインデックスを削除/無効にします。AXに外部キーがある場合は、それをドロップします。
  3. A.B_IDのみを含むインデックスを作成し、B.IDには別のインデックスを作成します。
  4. 単純復旧モデルを使用している場合でも、最後のトランザクションは、ディスクにフラッシュされる前に常にトランザクションログに記録されます。そのため、トランザクションログを事前に拡張し、それよりも大きなサイズ(100 MBなど)に拡張するように設定する必要があります。
  5. また、データファイルの増加量をある程度大きく設定します。
  6. ログファイルとデータファイルをさらに拡張するのに十分なディスク領域があることを確認してください。
  7. 更新が完了すると、手順2で削除/無効にしたインデックスが再作成/有効になります。
  8. 不要になった場合は、手順3で作成したインデックスを削除します。

編集: 元の投稿にはコメントできないため、ここでは編集4からの質問に回答します。AXインデックスには7つのインデックスがあり、Bツリーであり、そのフィールドを更新するたびにBツリーが再調整されます。インデックスを毎回再調整するよりも、これらのインデックスを最初から再構築する方が高速です。


ポイント1については、ik_zelfに対する私の回答を参照してください。カーソルは調査の理由でそこにあり、それほど大きな影響はありません。私はあなたの提案の残りを実装するつもりです、それは私がやっていることすべてです。それが機能する場合、私はまだ何が起こっているのかについての説明なしに残されます...
GFK

テーブル(すべてのインデックス、制約などを含む)のDDLを投稿できます。多分あなたのパフォーマンスを遅くしている何かがあり、あなたはそれを見逃しています。
bojan 2013年

1
インデックスの削除/インデックスの更新/再構築は機能します。私は、劇的なことをする必要がないことを望みますが、選択肢があるとは思いません。ありがとう!
GFK 2013年

0

注目すべき点の1つは、このプロセス中のシステムリソース(メモリ、ディスク、CPU)です。1つの大きなジョブで1つのテーブルに700万行を挿入しようとすると、サーバーが同様にハングしました。

この大量挿入ジョブを実行するのに十分なメモリがサーバーにないことがわかりました。このような状況では、SQLはメモリを保持し、それを手放さないようにしています。...挿入コマンドが完了した後でも完了していない場合もあります。大きなジョブで処理されるコマンドが多いほど、より多くのメモリが消費されます。すばやく再起動すると、このメモリが解放されました。

タスクマネージャーを実行して、このプロセスを最初から開始します。メモリ使用量が75%を超えると、システム/プロセスが天文学的に急上昇する可能性があります。

上記のように実際にメモリ/リソースが制限されている場合、1つの大きなジョブではなく、プロセスをより小さな部分に分割する(メモリ使用率が高い場合は再起動する)か、大量のメモリを搭載した64ビットサーバーにアップグレードするかを選択できます。


0

更新シナリオは、手順を使用するよりも常に高速です。

テーブルAのすべての行の列Xを更新しているので、最初にその行のインデックスを削除してください。また、その列でアクティブなトリガーや制約などがないことを確認してください。

インデックスの更新は、制約の検証や他のデータの検索を行う行レベルのトリガーの実行と同様に、コストのかかるビジネスです。


それはポイントではないと思います。インデックス付けされたレコードの更新には時間がかかることを理解しています。全体として、時間がかかるのはこれが原因であることがわかっています。しかし、私はこれを期待し、それで大丈夫です:前述のとおり、99%の行の更新には5分(カーソルを使用しても)かかりますが、何らかの理由で、1行(常に同じではない)に5時間かかります。私が心配しているのは、この特定の行動です。
GFK 2013年

ロックはあなたが言った問題ではありません...ファイルシステムの使用率はどうですか、90%以上に達していますか?
ik_zelf 2013年

いいえ、120 GBのうち31 GBが無料なので、大丈夫だと思います
GFK

create table a_copy as select * from a;のようにテーブルをコピーしようとするとどうなりますか。
ik_zelf 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.