Saya harus menghapus 16+ jutaan rekaman dari tabel baris 221+ juta dan ini berjalan sangat lambat.
Saya menghargai jika Anda berbagi saran untuk membuat kode di bawah ini lebih cepat:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @BATCHSIZE > 0
BEGIN
DELETE TOP (@BATCHSIZE) FROM MySourceTable
OUTPUT DELETED.*
INTO MyBackupTable
WHERE NOT EXISTS (
SELECT NULL AS Empty
FROM dbo.vendor AS v
WHERE VendorId = v.Id
);
SET @BATCHSIZE = @@ROWCOUNT;
SET @ITERATION = @ITERATION + 1;
SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);
PRINT @MSG;
COMMIT TRANSACTION;
CHECKPOINT;
END;
END TRY
BEGIN CATCH
IF @@ERROR <> 0
AND @@TRANCOUNT > 0
BEGIN
PRINT 'There is an error occured. The database update failed.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
Rencana Eksekusi (terbatas untuk 2 iterasi)
VendorId
adalah PK dan non-clustered , di mana indeks clustered tidak digunakan oleh skrip ini. Ada 5 indeks non-unik dan non-cluster lainnya.
Tugas adalah "menghapus vendor yang tidak ada di tabel lain" dan mencadangkannya ke tabel lain. Saya punya 3 tabel vendors, SpecialVendors, SpecialVendorBackups
,. Mencoba untuk menghapus SpecialVendors
yang tidak ada dalam Vendors
tabel, dan memiliki cadangan catatan yang terhapus kalau-kalau apa yang saya lakukan salah dan saya harus mengembalikannya dalam satu atau dua minggu.
sql-server
query-performance
delete
cilerler
sumber
sumber
Jawaban:
Rencana pelaksanaan menunjukkan bahwa ia membaca baris dari indeks yang tidak dikelompokkan dalam beberapa urutan kemudian melakukan pencarian untuk setiap baris luar yang dibaca untuk mengevaluasi
NOT EXISTS
Anda menghapus 7,2% dari tabel. 16.000.000 baris dalam 3.556 bets 4.500
Dengan asumsi bahwa baris-baris yang memenuhi syarat akhirnya didistribusikan ke seluruh indeks maka ini berarti akan menghapus kira-kira 1 baris setiap 13,8 baris.
Jadi iterasi 1 akan membaca 62.156 baris dan melakukan banyak pencarian sebelum menemukan 4.500 untuk dihapus.
iterasi 2 akan membaca 57.656 (62.156 - 4.500) baris yang pasti tidak akan memenuhi syarat mengabaikan pembaruan bersamaan (karena mereka sudah diproses) dan kemudian 62.156 baris lain untuk mendapatkan 4.500 untuk dihapus.
iterasi 3 akan membaca (2 * 57.656) + 62.156 baris dan seterusnya hingga akhirnya iterasi 3.556 akan membaca (3.555 * 57.656) + 62.156 baris dan melakukan banyak pencarian.
Jadi jumlah indeks yang dilakukan di semua batch adalah
SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)
Yang mana
((3555 * 3556 / 2) * 57656) + (3556 * 62156)
- atau364,652,494,976
Saya akan menyarankan Anda mematerialisasi baris untuk menghapus ke tabel temp pertama
Dan ubah
DELETE
untuk menghapusWHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)
Anda mungkin masih perlu memasukkan aNOT EXISTS
dalamDELETE
kueri itu sendiri untuk memenuhi pembaruan karena tabel temp telah diisi tetapi ini harus jauh lebih efisien karena hanya perlu melakukan 4,500 pencarian per batch.sumber
PK
kolom? (Saya percaya Anda menyarankan saya untuk memindahkan mereka ke temp table sepenuhnya tetapi ingin memeriksa ulang)DELETE TOP (@BATCHSIZE) FROM MySourceTable
seharusnya hanyaDELETE FROM MySourceTable
juga mengindeks tabel tempCREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );
danVendorId
pasti PK sendiri? Anda memiliki> 221 juta vendor yang berbeda?Rencana pelaksanaan menunjukkan bahwa setiap loop berturut-turut akan melakukan lebih banyak pekerjaan daripada loop sebelumnya. Dengan asumsi bahwa baris yang dihapus dihapus secara merata di seluruh tabel, loop pertama harus memindai sekitar 4500 * 221000000/16000000 = 62156 baris untuk menemukan 4500 baris yang akan dihapus. Itu juga akan melakukan jumlah yang sama dari pencarian indeks berkerumun terhadap
vendor
tabel. Namun, loop kedua harus membaca melewati 62156 - 4500 = 57656 baris yang sama yang Anda tidak hapus pertama kali. Kita mungkin mengharapkan loop kedua untuk memindai 1.200 baris dariMySourceTable
dan untuk melakukan 1.200 berusaha melawanvendor
tabel. Jumlah pekerjaan yang dibutuhkan per loop meningkat pada tingkat linier. Sebagai perkiraan, kita dapat mengatakan bahwa loop rata-rata perlu membaca 102516868 baris dariMySourceTable
dan untuk melakukan 102516868 berusaha melawanvendor
meja. Untuk menghapus 16 juta baris dengan ukuran batch 4.500 kode Anda perlu melakukan 16000000/4500 = 3556 loop, sehingga jumlah total pekerjaan untuk menyelesaikan kode Anda adalah sekitar 364,5 miliar baris yang dibaca dariMySourceTable
dan 364,5 miliar indeks yang dicari.Masalah yang lebih kecil adalah bahwa Anda menggunakan variabel lokal
@BATCHSIZE
dalam ekspresi TOP tanpaRECOMPILE
atau beberapa petunjuk lainnya. Pengoptimal kueri tidak akan tahu nilai variabel lokal itu saat membuat paket. Itu akan menganggap bahwa itu sama dengan 100. Pada kenyataannya Anda menghapus 4.500 baris, bukan 100, dan Anda mungkin bisa berakhir dengan rencana yang kurang efisien karena perbedaan itu. Perkiraan kardinalitas rendah ketika memasukkan ke dalam tabel dapat menyebabkan kinerja juga. SQL Server mungkin memilih API internal yang berbeda untuk melakukan sisipan jika dianggap perlu memasukkan 100 baris dibandingkan dengan 4500 baris.Salah satu alternatif adalah dengan cukup memasukkan kunci utama / kunci berkerumun dari baris yang ingin Anda hapus ke tabel sementara. Bergantung pada ukuran kolom kunci Anda, ini dapat dengan mudah masuk ke tempdb. Anda bisa mendapatkan log minimal dalam kasus itu yang berarti bahwa log transaksi tidak akan meledak. Anda juga bisa mendapatkan pencatatan minimum terhadap basis data apa pun dengan model pemulihan
SIMPLE
. Lihat tautan untuk informasi lebih lanjut tentang persyaratan.Jika itu bukan opsi maka Anda harus mengubah kode Anda sehingga Anda dapat memanfaatkan indeks berkerumun di
MySourceTable
. Kuncinya adalah menulis kode Anda sehingga Anda melakukan kira-kira jumlah pekerjaan yang sama per loop. Anda dapat melakukannya dengan memanfaatkan indeks alih-alih hanya memindai tabel dari awal setiap kali. Saya menulis posting blog yang membahas beberapa metode pengulangan yang berbeda. Contoh-contoh dalam postingan tersebut memasukkan ke dalam tabel alih-alih menghapus tetapi Anda harus dapat mengadaptasi kode.Dalam contoh kode di bawah ini saya menganggap bahwa kunci utama dan kunci cluster Anda
MySourceTable
. Saya menulis kode ini dengan cepat dan tidak dapat mengujinya:Bagian kuncinya ada di sini:
Setiap loop hanya akan membaca 60000 baris dari
MySourceTable
. Itu akan menghasilkan ukuran penghapusan rata-rata 4.500 baris per transaksi dan ukuran penghapusan maksimum 60000 baris per transaksi. Jika Anda ingin lebih konservatif dengan ukuran batch yang lebih kecil, itu bagus juga. The@STARTID
kemajuan variabel setelah setiap loop sehingga Anda dapat menghindari membaca baris yang sama lebih dari sekali dari tabel sumber.sumber
Dua pemikiran muncul di benak:
Penundaan mungkin karena pengindeksan dengan volume data itu. Coba jatuhkan indeks, hapus, dan bangun kembali indeks.
Atau..
Mungkin lebih cepat untuk menyalin baris yang ingin Anda simpan ke tabel sementara, jatuhkan tabel dengan 16 juta baris, dan ganti nama tabel sementara (atau salin ke instance baru dari tabel sumber).
sumber