Yürütme planı, birbirini izleyen her döngünün önceki döngüden daha fazla iş yapacağını gösterir. Silinecek satırların tablo boyunca eşit olarak dağıtıldığı varsayılarak, ilk döngü silinecek 4500 satırı bulmak için yaklaşık 4500 * 221000000/16000000 = 62156 satırı taramalıdır. Ayrıca, vendor
tabloya göre aynı sayıda kümelenmiş dizin arar . Ancak, ikinci döngünün ilk kez silmediğiniz 62156 - 4500 = 57656 satırlarını geçmesi gerekir. İkinci döngünün 120000 satırı taramasını MySourceTable
ve 120000 aramasını vendor
tabloya göre yapmasını bekleyebiliriz . Döngü başına gereken iş miktarı doğrusal bir oranda artar. Yaklaşık bir değer olarak biz ortalama döngü gelen gelen 102516868 satırları okumak gerekir söyleyebiliriz MySourceTable
ve 102516868 karşı istiyor yapmakvendor
tablo. 4500 toplu iş büyüklüğüne sahip 16 milyon satırı silmek için kodunuzun 16000000/4500 = 3556 döngü yapması gerekir, bu nedenle kodunuzun tamamlanması için gereken toplam çalışma miktarı 364,5 milyar satır okur MySourceTable
ve 364,5 milyar dizin arar.
Daha küçük bir sorun, @BATCHSIZE
TOP ifadesinde yerel bir değişkeni bir RECOMPILE
veya başka ipucu olmadan kullanmanızdır . Sorgu iyileştirici, bir plan oluştururken bu yerel değişkenin değerini bilmeyecektir. Gerçekte 100'e eşit olduğunu varsayacaktır. Gerçekte 100 yerine 4500 satır siliyorsunuz ve muhtemelen bu tutarsızlık nedeniyle daha az verimli bir plan elde edebilirsiniz. Bir tabloya eklenirken düşük kardinalite tahmini performans performansına da neden olabilir. SQL Server, 4500 satır yerine 100 satır eklemesi gerektiğini düşünüyorsa, ekleme yapmak için farklı bir dahili API seçebilir.
Alternatiflerden biri, silmek istediğiniz satırların birincil anahtarlarını / kümelenmiş anahtarlarını geçici bir tabloya eklemektir. Anahtar sütunlarınızın boyutuna bağlı olarak bu kolaylıkla tempdb'ye sığabilir. Bu durumda minimum günlük kaydı alabilirsiniz , yani işlem günlüğü patlamaz. Ayrıca bir kurtarma modeli ile herhangi bir veritabanına karşı en az günlük alabilirsiniz SIMPLE
. Gereksinimler hakkında daha fazla bilgi için bağlantıya bakın.
Bu bir seçenek değilse, kümelenmiş dizinden yararlanabilmeniz için kodunuzu değiştirmeniz gerekir MySourceTable
. Önemli olan, her döngüde yaklaşık aynı miktarda iş yapabilmeniz için kodunuzu yazmaktır. Bunu, her seferinde tabloyu baştan taramak yerine dizinden yararlanarak yapabilirsiniz. Bazı farklı döngü yöntemlerinin üzerinden geçen bir blog yazısı yazdım . Bu gönderideki örnekler, silmek yerine tabloya eklenir, ancak kodu uyarlayabilmeniz gerekir.
Aşağıdaki örnek kodda, birincil anahtarın ve kümelenmiş anahtarınızın olduğunu varsayalım MySourceTable
. Bu kodu oldukça hızlı bir şekilde yazdım ve test edemiyorum:
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
Anahtar kısım burada:
WITH MySourceTable_DELCTE AS (
SELECT TOP (60000) *
FROM MySourceTable
WHERE ID >= @STARTID
ORDER BY ID
)
Her döngü yalnızca 60000 satırı okuyacaktır MySourceTable
. Bu işlem başına ortalama 4500 satırlık silme boyutu ve işlem başına maksimum 60000 satırlık silme boyutu ile sonuçlanmalıdır. Daha küçük bir parti boyutu ile daha muhafazakar olmak istiyorsanız, bu da iyi. @STARTID
Her döngü sonra değişken gelişmeler daha kaynak tablodan birden fazla kez aynı satır okuma önlemek, böylece.