Yavaş Performans Büyük Tabloya Birkaç Satır Ekleme


9

Mağazalardaki verileri alan ve şirket genelindeki bir envanter tablosunu güncelleyen bir işlemimiz var. Bu tabloda, her mağaza için tarihe ve öğeye göre satırlar bulunur. Birçok mağazaya sahip müşteriler için, bu tablo çok büyük olabilir - 500 milyon sıraya kadar.

Bu envanter güncelleme işlemi, mağazalar veri girdikçe genellikle günde birçok kez çalıştırılır. Bunlar yalnızca birkaç mağazadan güncelleme verilerini çalıştırır. Bununla birlikte, müşteriler bunu son 30 gün içindeki tüm mağazaları güncellemek için de çalıştırabilir. Bu durumda, işlem 10 iş parçacığı döndürür ve her mağazanın envanterini ayrı bir iş parçacığında günceller.

Müşteri, sürecin uzun sürdüğünden şikayet ediyor. Süreci profilli ve bu tabloya INSERTs bir sorgu beklediğimden çok daha fazla zaman harcadığını bulundu. Bu INSERT bazen 30 saniye içinde tamamlanır.

Bu tabloya karşı geçici bir SQL INSERT komutu çalıştırdığımda (BEGIN TRAN ve ROLLBACK ile sınırlı), geçici SQL milisaniye sırasına göre tamamlanır.

Yavaş performans gösteren sorgu aşağıdadır. Fikir, orada olmayan kayıtları eklemek ve daha sonra çeşitli veri bitlerini hesaplarken bunları GÜNCELLEMEK. Süreçteki bir önceki adım, güncellenmesi gereken öğeleri belirledi, bazı hesaplamalar yaptı ve sonuçları temp_d_ Update_Item_Work tempdb tablosuna doldurdu. Bu işlem 10 ayrı iş parçacığında çalışır ve her iş parçacığının Update_Item_Work içinde kendi GUID değeri vardır.

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)

Envanter tablosunda, çoğu çeşitli envanter ayarlamaları için miktarları ve sayıları izleyen 42 sütun bulunur. sys.dm_db_index_physical_stats her satırın yaklaşık 242 bayt olduğunu söylüyor, bu yüzden yaklaşık 33 satırın tek bir 8 KB'lik sayfaya sığacağını umuyorum.

Tablo benzersiz kısıtlamayla (Inv_Site_Key, Inv_Item_Key, Inv_Date) kümelenmiştir. Tüm tuşlar DECIMAL (15,0) ve tarih SMALLDATETIME. Bir IDENTITY birincil anahtarı (kümelenmemiş) ve diğer 4 dizin vardır. Tüm dizinler ve kümelenmiş kısıtlama açık bir şekilde tanımlanır (FILLFACTOR = 90, PAD_INDEX = ON).

Sayfa bölünmelerini saymak için günlük dosyasına baktım. Kümelenmiş dizinde yaklaşık 1.027 bölme ve başka bir dizinde 1.724 bölme ölçtüm, ancak bunların hangi aralıkta gerçekleştiğini kaydetmedim. Bir buçuk saat sonra, kümelenmiş dizinde 7.035 sayfa bölünmesi ölçtüm.

Profil oluşturucuda yakaladığım sorgu planı şöyle:

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                                                                                       

Sorgulara vs çeşitli dmv bakarak, ben bu Envanter tablosundaki bir sayfada 0 süre PAGEIOLATCH_EX sorgu bekliyor görüyorum. Ben kilit beklemek veya engelleme görmüyorum.

Bu makinenin yaklaşık 32 GB belleği vardır. Yakında 2008 R2 Enterprise Edition'a yükseltmekle birlikte SQL Server 2005 Standard Edition çalıştırıyor. Envanter tablosunun disk kullanımı açısından ne kadar büyük olduğuna dair rakamlarım yok, ancak gerekirse alabilirim. Bu sistemdeki en büyük tablolardan biridir.

Sys.dm_io_virtual_file_stats karşı bir sorgu çalıştırdı ve tempdb karşı ortalama yazma beklemeleri 1.1 saniye yukarı olduğunu gördüm . Bu tablonun saklandığı veritabanı ~ 350 ms ortalama yazma beklemesine sahiptir. Ancak sunucularını yalnızca 6 ayda bir yeniden başlatırlar, bu nedenle bu bilgilerin alakalı olup olmadığı hakkında hiçbir fikrim yok. tempdb 4 farklı dosyaya dağıtılır Envanter tablosunu tutan veritabanı için 3 farklı dosyaya sahiptir.

Neden tek bir INSERT çok hızlı olduğunda bu sorgu birçok farklı iş parçacığı ile çalıştırıldığında birkaç satır INSERT kadar uzun sürer?

-- GÜNCELLEME --

İşte okunan bayt dahil sürücü başına gecikme sayıları. Gördüğünüz gibi tempdb performansı şüphelidir. Envanter tablosu PDICompany_252_01.mdf, PDICompany_252_01_Second.ndf veya PDICompany_252_01_Third.ndf dizinindedir.

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

Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Paul White 9

Yanıtlar:


4

Kümelenmiş dizin sayfası bölünmelerinizin ağrılı olacağı anlaşılıyor çünkü kümelenmiş dizin gerçek verileri tutuyor ve bunun için yeni sayfaların atanması ve verilerin bunlara taşınması gerekecek. Bunun sayfa kilitlenmesine ve dolayısıyla engellenmesine neden olması muhtemeldir.

Ayrıca, kümelenmiş dizin anahtarınızın 21 bayt olduğunu ve bunun tüm ikincil dizinlerinizde yer işareti olarak saklanması gerektiğini unutmayın.

Birincil anahtar kimlik sütununuzu kümelenmiş dizininiz yapmayı düşündünüz mü, bu yalnızca diğer dizinlerinizin boyutunu küçültmekle kalmaz, aynı zamanda kümelenmiş dizininizdeki sayfa bölme sayısını da azaltacağınız anlamına gelir. Eğer endekslerinizi mide yeniden inşa edebilirsiniz eğer denemeye değer.


1

Çok iş parçacıklı yaklaşımla, önce bir anahtarın varlığını kontrol etmeniz gereken bir tabloya ekleme konusunda ihtiyatlıyım. Bana öyle geliyor ki, kaç PK parçacığı olursa olsun, o PK endeksinde o tabloya bir eşzamanlılık sorunu olduğunu söylüyor. Aynı nedenle, farklı iş parçacığı aynı anahtarı yazabiliyorsa bir hata oluşabileceğinden envanter tablosundaki NOLOCK ipucunu sevmiyorum (bölümleme şeması bu olasılığı kaldırıyor mu?). Birden fazla iş parçacığının ilk girişinde hızlanmanın ne kadar büyük olduğunu merak ediyorum çünkü bir noktada iyi çalışmış olmalı.

Denenecek bir şey, sorgunun toplu bir işlem gibi daha fazla okunmasını sağlamak ve "olmayan yerlerde" bir "anti-birleştirme" ye dönüştürmektir. (sonuçta optimizer bu çabayı göz ardı etmeyi seçebilir). Yukarıda belirtildiği gibi, bölümleme, iş parçacıkları arasında herhangi bir anahtar çarpışması garanti etmedikçe, hedef tablodaki NOLOCK ipucunu kaldıracağım.

 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

Temel olarak çalışan zamanlama, başka bir olasılık olarak birleştirme ipucu ("sol birleştirme" -> "sol birleştirme birleştirme") ile yeniden çalıştırabilirsiniz. Büyük olasılıkla birleştirme ipucu için geçici tabloda (UpdItemWrk_Site_Key, UpdItemWrk_Item_Key, UpdItemWrk_Date) bir dizin olmalıdır.

SQL Server 2008/2012'nin daha yeni ifade olmayan sürümlerinin, GUID tabanlı bölümlemeyi kaldırmanıza izin veren bu formun daha büyük birleşmelerini otomatik olarak paralelleştirip etkinleştiremeyeceğini bilmiyorum.

Birleştirmeyi tüm öğeler yerine yalnızca farklı öğeler üzerinde gerçekleşmeye teşvik etmek için, "farklı ... seç ..." yan tümceleri, daha önce "seç * (farklı seç ...) ..." ifadelerine dönüştürülebilir birleştirme ile devam ediyor. Bu, yalnızca farklı birçok satırı filtreliyorsa fark edilebilir bir fark yaratabilir. Optimizer yine bu çabayı görmezden gelebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.