我们有一个流程,该流程从商店获取数据并更新公司范围的库存表。该表按日期和项目列出了每个商店的行。对于拥有许多商店的客户,此表可能会非常大-大约5亿行。
随着商店输入数据,此库存更新过程通常一天运行多次。这些运行仅更新来自少数商店的数据。但是,客户也可以运行此程序以更新例如过去30天中的所有商店。在这种情况下,该过程启动了10个线程,并在一个单独的线程中更新了每个商店的库存。
客户抱怨该过程需要很长时间。我已经概要分析了该过程,发现一个将INSERTs插入此表的查询所消耗的时间比我预期的要多得多。该插入有时会在30秒内完成。
当我对此表运行临时SQL INSERT命令(由BEGIN TRAN和ROLLBACK绑定)时,临时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)
库存表有42列,其中大多数跟踪各种库存调整的数量和计数。sys.dm_db_index_physical_stats表示每行约为242个字节,因此我希望在一个8 KB的页面上可以容纳约33行。
该表聚集在唯一约束(Inv_Site_Key,Inv_Item_Key,Inv_Date)上。所有键都是DECIMAL(15,0),日期是SMALLDATETIME。有一个IDENTITY主键(非聚集)和其他4个索引。所有索引和聚集约束均通过显式定义(FILLFACTOR = 90,PAD_INDEX = ON)。
我查看了日志文件以计算页面拆分。我在聚集索引上测量了约1,027个拆分,在另一个索引上测量了1,724个拆分,但是我没有记录这些拆分发生的间隔。一个半小时后,我测量了聚集索引的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。我没有关于磁盘使用情况的清单表的数量的数字,但是如有必要,我可以得到。它是此系统中最大的表之一。
我针对sys.dm_io_virtual_file_stats进行了查询,发现针对tempdb的平均写入等待时间超过1.1 秒。存储该表的数据库的平均写等待时间约为350毫秒。但是他们仅每6个月左右重新启动一次服务器,因此我不知道此信息是否相关。tempdb分布在4个不同的文件中。它们有3个不同的文件,用于存放Inventory表的数据库。
为什么在单个INSERT非常快的情况下,使用许多不同的线程运行时,此查询要花很长时间插入几行?
-更新-
这是每个驱动器的延迟数,包括读取的字节。如您所见,tempdb的性能令人怀疑。库存表位于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