İlgili soruya cevap verdiğimde bu sorunun farkında değildim ( Bu süre zarfında açık işlemlere ihtiyaç var mı? ), Ancak tamlık uğruna, bu bağlantılı cevaptaki önerimin bir parçası olmadığı için bu sorunu burada ele alacağım. .
Bunu bir SQL Agent işi aracılığıyla planlamayı önerdiğimden (sonuçta 100 milyon satır), istemciye (yani SSMS) durum mesajları göndermenin herhangi bir biçiminin ideal olacağını düşünmüyorum (eğer başka projelere ihtiyaç duymazsanız, Vladimir'e katılıyorum, kullanmanın RAISERROR('', 10, 1) WITH NOWAIT;yoludur).
Bu özel durumda, şimdiye kadar güncellenen satır sayısı ile her döngü başına güncellenebilen bir durum tablosu oluşturacağım. Ve süreçte bir kalp atışı yapmak için şimdiki zamanda atmak zarar vermez.
İşlemi iptal etmek ve yeniden başlatmak istediğiniz göz önüne alındığında, Ana tablonun GÜNCELLEME açık bir işlemde durum tablosunun GÜNCELLEME ile sarma yorgun. Ancak, durum tablosunun iptal nedeniyle hiç senkronize olmadığını düşünüyorsanız, sadece ile manuel olarak güncelleyerek mevcut değerle yenilemek kolaydır COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL.ve GÜNCELLEME iki tablo vardır (yani ana tablo ve durum tablosu), biz gereken senkronize şu iki masayı tutmak için açık bir işlem kullanmak, henüz bir at işlemini iptal edersem yetim bir işlem olan risk istemiyorum işlem başladıktan sonra işlemedikten sonra gelin. SQL Agent işini durdurmadığınız sürece bunu yapmak güvenli olmalıdır.
İşlemi durdurmadan nasıl durdurabilirsiniz? Durmasını isteyerek :-). Evet. Süreci bir "sinyal" ( kill -3Unix'e benzer ) göndererek, bir sonraki uygun anda (yani aktif işlem olmadığında!) Durmasını ve tüm güzel ve düzenli benzeri bir şekilde temizlenmesini isteyebilirsiniz.
Başka bir oturumda çalışan işlemle nasıl iletişim kurabilirsiniz? Mevcut durumunu size geri iletmek için oluşturduğumuz mekanizmayı kullanarak: durum tablosu. İşlemin her döngünün başında kontrol edeceği bir sütun eklememiz gerekiyor, böylece devam edip etmeyeceğinizi veya iptal edileceğini bilecek. Ve niyet, bunu bir SQL Agent işi olarak planlamak olduğundan (her 10 veya 20 dakikada bir çalışın), işlem başlıyorsa, geçici tabloyu 1 milyon satırla doldurmanın bir anlamı olmadığından, en başından da kontrol etmeliyiz. bir süre sonra çıkmak ve bu verilerin hiçbirini kullanmamak.
DECLARE @BatchRows INT = 1000000,
@UpdateRows INT = 4995;
IF (OBJECT_ID(N'dbo.HugeTable_TempStatus') IS NULL)
BEGIN
CREATE TABLE dbo.HugeTable_TempStatus
(
RowsUpdated INT NOT NULL, -- updated by the process
LastUpdatedOn DATETIME NOT NULL, -- updated by the process
PauseProcess BIT NOT NULL -- read by the process
);
INSERT INTO dbo.HugeTable_TempStatus (RowsUpdated, LastUpdatedOn, PauseProcess)
VALUES (0, GETDATE(), 0);
END;
-- First check to see if we should run. If no, don't waste time filling temp table
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. No need to start.';
RETURN;
END;
CREATE TABLE #FullSet (KeyField1 DataType1, KeyField2 DataType2);
CREATE TABLE #CurrentSet (KeyField1 DataType1, KeyField2 DataType2);
INSERT INTO #FullSet (KeyField1, KeyField2)
SELECT TOP (@BatchRows) ht.KeyField1, ht.KeyField2
FROM dbo.HugeTable ht
WHERE ht.deleted IS NULL
OR ht.deletedDate IS NULL
WHILE (1 = 1)
BEGIN
-- Check if process is paused. If yes, just exit cleanly.
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. Exiting.';
BREAK;
END;
-- grab a set of rows to update
DELETE TOP (@UpdateRows)
FROM #FullSet
OUTPUT Deleted.KeyField1, Deleted.KeyField2
INTO #CurrentSet (KeyField1, KeyField2);
IF (@@ROWCOUNT = 0)
BEGIN
RAISERROR(N'All rows have been updated!!', 16, 1);
BREAK;
END;
BEGIN TRY
BEGIN TRAN;
-- do the update of the main table
UPDATE ht
SET ht.deleted = 0,
ht.deletedDate = '2000-01-01'
FROM dbo.HugeTable ht
INNER JOIN #CurrentSet cs
ON cs.KeyField1 = ht.KeyField1
AND cs.KeyField2 = ht.KeyField2;
-- update the current status
UPDATE ts
SET ts.RowsUpdated += @@ROWCOUNT,
ts.LastUpdatedOn = GETDATE()
FROM dbo.HugeTable_TempStatus ts;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW; -- raise the error and terminate the process
END CATCH;
-- clear out rows to update for next iteration
TRUNCATE TABLE #CurrentSet;
WAITFOR DELAY '00:00:01'; -- 1 second delay for some breathing room
END;
-- clean up temp tables when testing
-- DROP TABLE #FullSet;
-- DROP TABLE #CurrentSet;
Ardından, aşağıdaki sorguyu kullanarak durumu istediğiniz zaman kontrol edebilirsiniz:
SELECT sp.[rows] AS [TotalRowsInTable],
ts.RowsUpdated,
(sp.[rows] - ts.RowsUpdated) AS [RowsRemaining],
ts.LastUpdatedOn
FROM sys.partitions sp
CROSS JOIN dbo.HugeTable_TempStatus ts
WHERE sp.[object_id] = OBJECT_ID(N'ResizeTest')
AND sp.[index_id] < 2;
Bir SQL Agent işinde veya başka birinin bilgisayarındaki SSMS'de çalışıyor olsa da işlemi duraklatmak ister misiniz? Sadece koş:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Sürecin yeniden başlatılmasını ister misiniz? Sadece koş:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
GÜNCELLEME:
İşte bu işlemin performansını artırabilecek bazı ek şeyler. Hiçbirinin yardım etmesi garanti edilmez, ancak muhtemelen test edilmeye değer. Ve güncellenecek 100 milyon satır ile, bazı varyasyonları test etmek için bolca zamanınız / fırsatınız var ;-).
TOP (@UpdateRows)UPDATE sorgusuna en üst satırın görünmesi için ekleyin :
UPDATE TOP (@UpdateRows) ht
Bazen en iyi duruma getiricinin kaç satırın etkileneceğini bilmesini sağlar, böylece daha fazlasını aramak için zaman kaybetmez.
#CurrentSetGeçici tabloya bir birincil anahtar ekleyin . Buradaki fikir, optimize edicinin 100 milyon sıra tablosuna JOIN ile yardım etmektir.
Ve sadece belirsiz olmamasını sağlamak için, #FullSetgeçici tabloya PK eklemek için herhangi bir sebep olmamalıdır, çünkü bu sadece siparişin alakasız olduğu basit bir kuyruk tablosudur.
- Bazı durumlarda
SELECT, #FullSetgeçici tabloya beslenmesine yardımcı olmak için bir Filtrelenmiş Dizin eklemek yardımcı olur . Bu tür bir dizin eklemeyle ilgili bazı noktalar:
- WHERE koşulu, sorgunuzun WHERE koşuluyla eşleşmelidir;
WHERE deleted is null or deletedDate is null
- İşlemin başlangıcında, çoğu satır WHERE durumunuzla eşleşir, bu nedenle bir dizin o kadar da yararlı değildir. Bunu eklemeden önce% 50 civarında bir yere kadar beklemek isteyebilirsiniz. Tabii ki, ne kadar yardımcı olur ve endeksi eklemenin en iyi olduğu zaman birkaç faktörden dolayı değişir, bu yüzden biraz deneme yanılmadır.
- Temel veriler oldukça sık değiştiği için, istatistikleri manuel olarak GÜNCELLEMEK ve / veya dizini tekrar güncellemeniz gerekebilir.
- Dizin, yardımcı olurken
SELECT, UPDATEbu işlem sırasında güncellenmesi gereken başka bir nesne, dolayısıyla daha fazla G / Ç olduğu için zarar vereceğini unutmayın . Bu, hem Filtrelenmiş Bir Dizini (daha az sayıda satır filtreyle eşleştiğinden satırları güncellerken küçülür) hem de dizini eklemek için biraz beklerken (başlangıçta süper yardımcı olmayacaksa, bunun için bir neden yok) ek G / Ç).