Performa Lambat Memasukkan Beberapa Baris Ke Dalam Tabel Besar

9

Kami memiliki proses yang mengambil data dari toko dan memperbarui tabel inventaris di seluruh perusahaan. Tabel ini memiliki baris untuk setiap toko berdasarkan tanggal dan berdasarkan item. Pada pelanggan dengan banyak toko, tabel ini bisa menjadi sangat besar - dalam urutan 500 juta baris.

Proses pembaruan inventaris ini biasanya dijalankan beberapa kali sehari ketika toko memasukkan data. Ini menjalankan pembaruan data dari hanya beberapa toko. Namun, pelanggan juga dapat menjalankan ini untuk memperbarui, katakanlah, semua toko dalam 30 hari terakhir. Dalam hal ini, proses tersebut memutar 10 utas dan memperbarui inventaris setiap toko dalam utas terpisah.

Pelanggan mengeluh bahwa prosesnya memakan waktu lama. Saya telah membuat profil proses dan menemukan bahwa satu permintaan yang memasukkan ke dalam tabel ini menghabiskan lebih banyak waktu daripada yang saya harapkan. INSERT ini terkadang selesai dalam 30 detik.

Ketika saya menjalankan perintah SQL INSERT ad-hoc terhadap tabel ini (dibatasi oleh BEGIN TRAN dan ROLLBACK), ad-hoc SQL selesai pada urutan milidetik.

Kueri berperforma lambat di bawah ini. Idenya adalah untuk MEMASANG catatan yang tidak ada di sana dan nanti untuk MEMPERBARUI mereka saat kami menghitung berbagai bit data. Langkah sebelumnya dalam proses telah mengidentifikasi item yang perlu diperbarui, melakukan beberapa perhitungan, dan memasukkan hasilnya ke tabel tempdb Update_Item_Work. Proses ini berjalan di 10 utas terpisah, dan masing-masing utas memiliki GUID sendiri di Update_Item_Work.

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)

Tabel Persediaan memiliki 42 kolom, yang sebagian besar melacak jumlah dan jumlah untuk berbagai penyesuaian inventaris. sys.dm_db_index_physical_stats mengatakan setiap baris sekitar 242 byte, jadi saya berharap sekitar 33 baris akan muat pada satu halaman 8 KB.

Tabel ini mengelompok pada batasan unik (Inv_Site_Key, Inv_Item_Key, Inv_Date). Semua kunci adalah DECIMAL (15,0), dan tanggalnya adalah SMALLDATETIME. Ada IDENTITY primary key (nonclustered) dan 4 indeks lainnya. Semua indeks dan batasan yang dikelompokkan ditentukan dengan eksplisit (FILLFACTOR = 90, PAD_INDEX = ON).

Saya melihat file log untuk menghitung pemisahan halaman. Saya mengukur sekitar 1.027 pemisahan pada indeks berkerumun dan 1.724 pemisahan pada indeks lain, tetapi saya tidak mencatat interval apa yang terjadi. Satu setengah jam kemudian, saya mengukur 7.035 pemisahan halaman pada indeks berkerumun.

Paket kueri yang saya tangkap di profiler terlihat seperti ini:

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                                                                                       

Melihat kueri vs berbagai dmv, saya melihat kueri sedang menunggu di PAGEIOLATCH_EX selama 0 di halaman dalam tabel Inventaris ini. Saya tidak melihat menunggu atau memblokir kunci.

Mesin ini memiliki sekitar 32 GB memori. Ini menjalankan SQL Server 2005 Edisi Standar, meskipun mereka segera memperbarui ke 2008 R2 Enterprise Edition. Saya tidak memiliki angka tentang seberapa besar tabel inventaris dalam hal penggunaan disk, tapi saya bisa mendapatkannya, jika perlu. Ini adalah salah satu tabel terbesar dalam sistem ini.

Saya menjalankan kueri terhadap sys.dm_io_virtual_file_stats dan melihat rata-rata penulisan menunggu terhadap tempdb hingga 1,1 detik . Database tempat tabel ini disimpan memiliki rata-rata waktu tunggu ~ 350 ms. Tetapi mereka hanya me-restart server mereka setiap 6 bulan atau lebih, jadi saya tidak tahu apakah informasi ini relevan. tempdb tersebar di 4 file berbeda Mereka memiliki 3 file berbeda untuk database yang menyimpan tabel Inventaris.

Mengapa kueri ini membutuhkan waktu lama untuk MASUKKAN beberapa baris saat dijalankan dengan banyak utas yang berbeda ketika satu INSERT sangat cepat?

- PEMBARUAN -

Berikut adalah nomor latensi per drive termasuk byte yang dibaca. Seperti yang Anda lihat, kinerja tempdb dipertanyakan. Tabel Persediaan ada di dalam PDICompany_252_01.mdf, PDICompany_252_01_Second.ndf, atau 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
Paul Williams
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Paul White 9

Jawaban:

4

Sepertinya pemisahan halaman indeks berkerumun Anda akan menjadi menyakitkan karena indeks berkerumun menyimpan data aktual dan ini akan membutuhkan halaman baru untuk dialokasikan dan data dipindahkan ke ini. Ini kemungkinan menyebabkan penguncian halaman dan karenanya memblokir.

Ingat juga bahwa kunci indeks berkerumun Anda adalah 21 byte dan ini harus disimpan di semua indeks sekunder Anda sebagai bookmark.

Sudahkah Anda mempertimbangkan membuat kolom identitas kunci utama Anda sebagai indeks berkerumun, tidak hanya ini akan mengurangi ukuran indeks Anda yang lain, itu juga akan berarti bahwa Anda akan mengurangi jumlah pemisahan halaman dalam indeks berkerumun Anda. Layak dicoba jika Anda dapat membangun kembali indeks Anda.

Steve
sumber
1

Dengan pendekatan multi-utas, saya khawatir memasukkan ke dalam tabel dari mana Anda harus terlebih dahulu memeriksa keberadaan kunci. Itu semacam mengatakan kepada saya bahwa ada masalah konkurensi pada indeks PK ke meja itu tidak peduli berapa banyak utas yang ada. Untuk alasan yang sama, saya tidak suka petunjuk NOLOCK di tabel inventaris karena sepertinya kesalahan akan terjadi jika utas yang berbeda dapat menulis kunci yang sama (apakah skema partisi menghapus kemungkinan itu?). Saya ingin tahu seberapa besar speedup pada pengenalan awal beberapa utas karena pasti telah bekerja dengan baik di beberapa titik.

Sesuatu untuk dicoba adalah membuat kueri dibaca lebih seperti operasi massal dan mengubah "di mana tidak ada" menjadi "anti-bergabung." (akhirnya pengoptimal dapat memilih untuk mengabaikan upaya ini). Seperti yang disebutkan di atas, saya akan menghapus petunjuk NOLOCK di tabel tujuan kecuali mungkin partisi tidak menjamin tabrakan kunci di antara utas.

 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

Waktu yang berjalan sebagai basis, Anda mungkin menjalankan kembali dengan petunjuk gabungan ("gabung kiri" -> "gabung bergabung kiri") sebagai kemungkinan lain. Anda mungkin harus memiliki indeks di tabel temp (UpdItemWrk_Site_Key, UpdItemWrk_Item_Key, UpdItemWrk_Date) untuk petunjuk penggabungan.

Saya tidak tahu apakah versi SQL Server 2008/2012 non-express yang lebih baru akan dapat secara paralel melakukan penggabungan yang lebih besar dari formulir ini yang memungkinkan Anda untuk menghapus partisi berbasis GUID.

Untuk mendorong bergabung hanya terjadi pada item yang berbeda daripada semua item, klausa "pilih berbeda ... dari ..." dapat dikonversi menjadi "pilih * dari (pilih berbeda ... dari ...)" sebelum melanjutkan bergabung. Ini mungkin hanya membuat perbedaan nyata jika perbedaannya memfilter banyak baris. Sekali lagi pengoptimal mungkin mengabaikan upaya ini.

crokusek
sumber