慢的性能将几行插入到巨大的表


9

我们有一个流程,该流程从商店获取数据并更新公司范围的库存表。该表按日期和项目列出了每个商店的行。对于拥有许多商店的客户,此表可能会非常大-大约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

评论不作进一步讨论;此对话已转移至聊天
保罗·怀特9

Answers:


4

集群索引页面拆分似乎很麻烦,因为集群索引保存了实际数据,这将需要分配新页面并将数据移至这些页面。这很可能导致页面锁定并因此阻塞。

还要记住,聚集索引键是21个字节,这将需要作为书签存储在所有辅助索引中。

您是否考虑过将主键标识列作为聚簇索引,这不仅会减少其他索引的大小,还意味着将减少聚簇索引中的页面拆分数。如果您可以尝试重建索引,则值得尝试。


1

使用多线程方法时,我对插入表中的表当心,您必须首先从表中检查键的存在性。那种对我说,不管有多少线程,该表的PK索引上都存在并发问题。出于同样的原因,我不喜欢清单表上的NOLOCK提示,因为如果不同的线程能够写入相同的键,则似乎会发生错误(分区方案是否消除了这种可能性?)。我很好奇最初引入多线程的速度有多大,因为它一定在某些时候运行良好。

可以尝试使查询更像批量操作,并将“不存在的地方”转换为“反联接”。(最终,优化器可能会选择忽略此工作)。如上所述,除非分区保证线程之间没有键冲突,否则我将删除目标表上的NOLOCK提示。

 INSERT INTO i (...)
 SELECT DISTINCT ...             
   FROM tempdb..Update_Item_Work t (NOLOCK) -- nolock okay on read table
   left join Inventory i -- use without NOLOCK because PK is written inter-thread
     on i.Inv_Site_Key = t.UpdItemWrk_Site_Key
    and i.Inv_Item_Key = t.UpdItemWrk_Item_Key
    and i.Inv_Date = t.UpdItemWrk_Date
  where i.Inv_Site_Key is null   -- where not exist in inventory
    and UpdItemWrk_GUID = @GUID  -- for this thread

作为基础运行的时间,您可以使用合并提示(“左连接”->“左合并连接”)重新运行,作为另一种可能性。您可能应该在临时表上有一个用于合并提示的索引(UpdItemWrk_Site_Key,UpdItemWrk_Item_Key,UpdItemWrk_Date)。

我不知道SQL Server 2008/2012的较新非表达版本是否能够自动并行化此表单的较大合并,从而允许您删除基于GUID的分区。

为了鼓励联接仅在不同的项目而不是所有项目上发生,可以在将“选择不同的...从...”子句转换为“选择*从(选择...从...选择”)之前继续加入。如果不重复项过滤了很多行,这可能只会产生明显的不同。同样,优化器可能会忽略此工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.