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 vendortabel. 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 dari MySourceTabledan untuk melakukan 1.200 berusaha melawan vendortabel. Jumlah pekerjaan yang dibutuhkan per loop meningkat pada tingkat linier. Sebagai perkiraan, kita dapat mengatakan bahwa loop rata-rata perlu membaca 102516868 baris dari MySourceTabledan untuk melakukan 102516868 berusaha melawanvendormeja. 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 dari MySourceTabledan 364,5 miliar indeks yang dicari.
Masalah yang lebih kecil adalah bahwa Anda menggunakan variabel lokal @BATCHSIZEdalam ekspresi TOP tanpa RECOMPILEatau 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:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500)
@STARTID BIGINT,
@NEXTID BIGINT;
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
SELECT @STARTID = ID
FROM MySourceTable
ORDER BY ID
OFFSET 0 ROWS
FETCH FIRST 1 ROW ONLY;
SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @STARTID IS NOT NULL
BEGIN
WITH MySourceTable_DELCTE AS (
SELECT TOP (60000) *
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
)
DELETE FROM MySourceTable_DELCTE
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;
SET @STARTID = @NEXTID;
SET @NEXTID = NULL;
SELECT @NEXTID = ID
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
OFFSET (60000) ROWS
FETCH FIRST 1 ROW ONLY;
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
Bagian kuncinya ada di sini:
WITH MySourceTable_DELCTE AS (
SELECT TOP (60000) *
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
)
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 @STARTIDkemajuan variabel setelah setiap loop sehingga Anda dapat menghindari membaca baris yang sama lebih dari sekali dari tabel sumber.