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
sumber
Jawaban:
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.
sumber
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.
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.
sumber