店舗からデータを取得し、会社全体の在庫表を更新するプロセスがあります。このテーブルには、日付別およびアイテム別のすべてのストアの行があります。多くの店舗を持つ顧客では、このテーブルは非常に大きくなる可能性があり、5億行程度になります。
この在庫更新プロセスは、通常、ストアがデータを入力するときに1日に何度も実行されます。これらの実行は、ほんの数店舗のデータを更新します。ただし、これを実行して、たとえば過去30日間のすべての店舗を更新することもできます。この場合、プロセスは10のスレッドを起動し、各ストアの在庫を別のスレッドで更新します。
お客様から、プロセスに時間がかかっているとの不満が寄せられています。プロセスのプロファイルを作成したところ、このテーブルにINSERTを実行する1つのクエリが予想以上に多くの時間を消費していることがわかりました。このINSERTは、30秒で完了する場合があります。
このテーブルに対してBEGIN TRANとROLLBACKで区切られたアドホックSQL INSERTコマンドを実行すると、アドホックSQLはミリ秒のオーダーで完了します。
パフォーマンスの遅いクエリは次のとおりです。アイデアは、そこにないレコードを挿入し、後でデータのさまざまなビットを計算するときにそれらを更新することです。プロセスの前のステップでは、更新する必要のあるアイテムを特定し、いくつかの計算を行い、結果をtempdbテーブルUpdate_Item_Workに詰め込みました。このプロセスは10個の個別のスレッドで実行されており、各スレッドはUpdate_Item_Workに独自のGUIDを持っています。
INSERT INTO Inventory
(
Inv_Site_Key,
Inv_Item_Key,
Inv_Date,
Inv_BusEnt_ID,
Inv_End_WtAvg_Cost
)
SELECT DISTINCT
UpdItemWrk_Site_Key,
UpdItemWrk_Item_Key,
UpdItemWrk_Date,
UpdItemWrk_BusEnt_ID,
(CASE UpdItemWrk_Set_WtAvg_Cost WHEN 1 THEN UpdItemWrk_WtAvg_Cost ELSE 0 END)
FROM tempdb..Update_Item_Work (NOLOCK)
WHERE UpdItemWrk_GUID = @GUID
AND NOT EXISTS
-- Only insert for site/item/date combinations that don't exist
(SELECT *
FROM Inventory (NOLOCK)
WHERE Inv_Site_Key = UpdItemWrk_Site_Key
AND Inv_Item_Key = UpdItemWrk_Item_Key
AND Inv_Date = UpdItemWrk_Date)
Inventoryテーブルには42列があり、そのほとんどがさまざまな在庫調整の数量とカウントを追跡します。sys.dm_db_index_physical_statsは、各行が約242バイトであるため、約33行が単一の8 KBページに収まると予想しています。
テーブルは一意制約(Inv_Site_Key、Inv_Item_Key、Inv_Date)でクラスター化されます。すべてのキーはDECIMAL(15,0)であり、日付はSMALLDATETIMEです。IDENTITY主キー(非クラスター化)と4つの他のインデックスがあります。すべてのインデックスとクラスター化制約は明示的に定義されています(FILLFACTOR = 90、PAD_INDEX = ON)。
ログファイルを調べて、ページ分割をカウントしました。クラスタ化インデックスで約1,027分割、別のインデックスで1,724分割を測定しましたが、それらが発生した間隔を記録していません。1時間半後、クラスター化インデックスで7,035ページの分割を測定しました。
プロファイラーでキャプチャしたクエリプランは次のようになります。
Rows Executes StmtText
---- -------- --------
490 1 Sequence
0 1 |--Index Update
0 1 | |--Collapse
0 1 | |--Sort
0 1 | |--Filter
996 1 | |--Table Spool
996 1 | |--Split
498 1 | |--Assert
0 0 | |--Compute Scalar
498 1 | |--Clustered Index Update(UK_Inventory)
498 1 | |--Compute Scalar
0 0 | |--Compute Scalar
0 0 | |--Compute Scalar
498 1 | |--Compute Scalar
498 1 | |--Top
498 1 | |--Nested Loops
498 1 | |--Stream Aggregate
0 0 | | |--Compute Scalar
498 1 | | |--Clustered Index Seek(tempdb..Update_Item_Work)
498 498 | |--Clustered Index Seek(Inventory)
0 1 |--Index Update(UX_Inv_Exceptions_Date_Site_Item)
0 1 | |--Collapse
0 1 | |--Sort
0 1 | |--Filter
996 1 | |--Table Spool
490 1 |--Index Update(UX_Inv_Date_Site_Item)
490 1 |--Collapse
980 1 |--Sort
980 1 |--Filter
996 1 |--Table Spool
クエリとさまざまなdmvを比較すると、このインベントリテーブルのページで、クエリがPAGEIOLATCH_EXで0の期間待機していることがわかります。 ロックの待機やブロッキングはありません。
このマシンのメモリは約32 GBです。SQL Server 2005 Standard Editionを実行していますが、間もなく2008 R2 Enterprise Editionにアップグレードされます。ディスク使用量に関してインベントリテーブルがどれほど大きいかはわかりませんが、必要に応じて取得できます。これは、このシステムで最大のテーブルの1つです。
私はsys.dm_io_virtual_file_statsに対してクエリを実行し、tempdbに対する平均書き込み待機が1.1 秒以上であることを確認しました。このテーブルが格納されているデータベースの平均書き込み待機時間は約350ミリ秒です。ただし、サーバーは6か月ごとに再起動するだけなので、この情報が適切かどうかはわかりません。tempdbは4つの異なるファイルに分散されています。それらには、Inventoryテーブルを保持するデータベース用に3つの異なるファイルがあります。
単一のINSERTが非常に高速であるときに、多くの異なるスレッドで実行すると、このクエリが数行をINSERTするのにとても時間がかかるのはなぜですか?
-更新-
以下は、読み取られたバイト数を含むドライブごとのレイテンシ数です。ご覧のとおり、tempdbのパフォーマンスには疑問があります。Inventoryテーブルは、PDICompany_252_01.mdf、PDICompany_252_01_Second.ndf、またはPDICompany_252_01_Third.ndfのいずれかにあります。
ReadLatencyWriteLatencyLatencyAvgBPerRead AvgBPerWriteAvgBPerTransferDriveDB physical_name
42 1112 623 62171 67654 65147R: tempdb R:\Microsoft SQL Server\Tempdb\tempdev1.mdf
38 1101 615 62122 67626 65109S: tempdb S:\Microsoft SQL Server\Tempdb\tempdev2.ndf
38 1101 615 62136 67639 65123T: tempdb T:\Microsoft SQL Server\Tempdb\tempdev3.ndf
38 1101 615 62140 67629 65119U: tempdb U:\Microsoft SQL Server\Tempdb\tempdev4.ndf
25 341 71 92767 53288 87009X: PDICompany X:\Program Files\PDI\Enterprise\Databases\PDICompany_Third.ndf
26 339 71 90902 52507 85345X: PDICompany X:\Program Files\PDI\Enterprise\Databases\PDICompany_Second.ndf
10 231 90 98544 60191 84618W: PDICompany_FRx W:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx.mdf
61 137 68 9120 9181 9125W: model W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\modeldev.mdf
36 113 97 9376 5663 6419V: model V:\Microsoft SQL Server\Logs\modellog.ldf
22 99 34 92233 52112 86304W: PDICompany W:\Program Files\PDI\Enterprise\Databases\PDICompany.mdf
9 20 10 25188 9120 23538W: master W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\master.mdf
20 18 19 53419 10759 40850W: msdb W:\Microsoft SQL Server\MSSQL.3\MSSQL\Data\MSDBData.mdf
23 18 19 947956 58304 110123V: PDICompany_FRx V:\Program Files\PDI\Enterprise\Databases\PDICompany_FRx_1.ldf
20 17 17 828123 55295 104730V: PDICompany V:\Program Files\PDI\Enterprise\Databases\PDICompany.ldf
5 13 13 12308 4868 5129V: master V:\Microsoft SQL Server\Logs\mastlog.ldf
11 13 13 22233 7598 8513V: PDIMaster V:\Program Files\PDI\Enterprise\Databases\PDIMaster.ldf
14 11 13 13846 9540 12598W: PDIMaster W:\Program Files\PDI\Enterprise\Databases\PDIMaster.mdf
13 11 11 22350 1107 1110V: msdb V:\Microsoft SQL Server\Logs\MSDBLog.ldf
17 9 9 745437 11821 23249V: PDIFoundation V:\Program Files\PDI\Enterprise\Databases\PDIFoundation.ldf
34 8 31 29490 33725 30031W: PDIFoundation W:\Program Files\PDI\Enterprise\Databases\PDIFoundation.mdf
5 8 8 61560 61236 61237V: tempdb V:\Microsoft SQL Server\Logs\templog.ldf
13 6 11 8370 35087 16785W: SAHost_Company01 W:\Program Files\PDI\Enterprise\Databases\SAHostCompany.mdf
2 6 5 56235 33667 38911W: SAHost_Company01 W:\Program Files\PDI\Enterprise\Databases\SAHost_Company_01_log.LDF