sql server: küçük parçalar halinde büyük tablodaki alanları güncelleme: ilerleme / durum nasıl alınır?


10

Çok büyük (100 milyon satır) bir masamız var ve üzerinde birkaç alanı güncellememiz gerekiyor.

Günlük nakliye, vb. İçin, açıkçası, ısırık boyutundaki işlemlerde tutmak istiyoruz.

  • Aşağıdaki hile yapacak mı?
  • Ve bazı çıktıları yazdırmayı nasıl sağlayabiliriz, böylece ilerlemeyi görebiliriz? (orada bir PRINT ifadesi eklemeyi denedik, ancak while döngüsü sırasında hiçbir şey çıkmadı)

Kod:

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

Yanıtlar:


12

İ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 ;-).

  1. 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.
  2. #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.

  3. 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:
    1. WHERE koşulu, sorgunuzun WHERE koşuluyla eşleşmelidir; WHERE deleted is null or deletedDate is null
    2. İş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.
    3. Temel veriler oldukça sık değiştiği için, istatistikleri manuel olarak GÜNCELLEMEK ve / veya dizini tekrar güncellemeniz gerekebilir.
    4. 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 / Ç).

1
Bu mükemmel. Şimdi çalıştırıyorum ve gün boyunca on line çalıştırabileceğimize dair sigara içiyor. Teşekkür ederim!
Jonesome

@samsmith Sürecin daha hızlı performans göstermesi için bazı fikirler olduğu için lütfen az önce eklediğim GÜNCELLEME bölümüne bakın.
Solomon Rutzky

GÜNCELLEME geliştirmeleri olmadan, yaklaşık 8 milyon güncelleme / saat alıyoruz ... @BatchRows 10000000 (on milyon) olarak ayarlandı
Jonesome Reinstate Monica

@samsmith Bu harika :) değil mi? : Zihin iki şey bulundurun ) 1 süreç olacak NEREDE maddesini eşleşen daha az satır var az ve şöyle bir süzülmüş bir dizin eklemek için iyi bir zaman olacağını neden dolayısıyla yavaşlatmak, ama zaten az olmayan bir filtreden indeksi eklendi Ben eğer o irade yardım veya zarar vermeyeceğim eminim bu yüzden başlamak, ama hala daha yakın yapılıyor alır gibi üretilen iş azaltmak için beklenir ve 2) Eğer olabilir azaltarak verimi arttırmak WAITFOR DELAYiçin yarım saniyelik ya da öylesine, ancak bu, eşzamanlılık ve büyük olasılıkla günlük gönderimi yoluyla ne kadar gönderileceği ile bir değiş tokuş.
Solomon Rutzky

Saatte 8 milyon satırdan memnunuz. Evet, yavaşladığını görebiliriz. Daha fazla dizin oluşturmakta tereddüt ediyoruz (çünkü tablo tüm yapı için kilitlidir). Birkaç kez yaptığımız şey, mevcut endeks üzerinde bir yeniden düzenleme yapmaktır (çünkü bu çevrimiçi).
Jonesome

4

İkinci kısmı cevaplama: döngü sırasında bazı çıktıların nasıl yazdırılacağı.

Ben sys admin bazen çalıştırmak zorunda birkaç uzun çalışan bakım prosedürleri var.

Onları SSMS'den çalıştırıyorum ve PRINTifadenin yalnızca tüm prosedür tamamlandıktan sonra SSMS'de gösterildiğini fark ettim .

Yani, RAISERRORdüşük ciddiyetle kullanıyorum :

DECLARE @VarTemp nvarchar(32);
SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;

SQL Server 2008 Standard ve SSMS 2012 kullanıyorum (11.0.3128.0). SSMS'de çalıştırmak için tam bir çalışma örneği:

DECLARE @VarCount int = 0;
DECLARE @VarTemp nvarchar(32);

WHILE @VarCount < 3
BEGIN
    SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
    --RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;
    --PRINT @VarTemp;

    WAITFOR DELAY '00:00:02';
    SET @VarCount = @VarCount + 1;
END

Yorum yaptığımda RAISERRORve yalnızca PRINTSSMS'deki Mesajlar sekmesindeki mesajları bıraktığımda , 6 saniye sonra tüm toplu iş bittikten sonra görünür.

Yorum yaptığımda PRINTve RAISERRORSSMS'deki Mesajlar sekmesindeki mesajları kullandığımda 6 saniye beklemeden görünür, ancak döngü ilerledikçe.

İlginç bir şekilde, her ikisini de kullandığımda RAISERRORve PRINTher iki mesajı da görüyorum. İlk önce mesaj gelir RAISERROR, sonra 2 saniye geciktirir, sonra birinci PRINTve ikinci RAISERRORvb.


Diğer durumlarda, ayrı bir ayrılmış logtablo kullanıyorum ve uzun süren işlemin geçerli durumunu ve zaman damgasını açıklayan bazı bilgileri içeren bir satırı tabloya eklemeliyim.

Uzun süreç çalışırken ben neler olduğunu görmek için periyodik SELECTolarak logtablodan.

Bunun belli bir yükü vardır, ancak daha sonra kendi hızımda inceleyebileceğim bir günlük (veya günlük geçmişi) bırakır.


SQL 2008/2014 tarihinde, yükseltici sonuçları göremiyoruz .... ne eksik?
Jonesome

@samsmith, tam bir örnek ekledim. Dene. Bu basit örnekte nasıl bir davranış elde ediyorsunuz?
Vladimir Baranov

2

Aşağıdaki gibi başka bir bağlantıdan izleyebilirsiniz:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT COUNT(*) FROM [huge-table] WHERE deleted IS NULL OR deletedDate IS NULL 

ne kadar kaldığını görmek için. Bir uygulama işlemi SSMS veya benzeri bir ortamda manuel olarak çalıştırmak yerine ilerleme kaydediyorsa ve ilerleme durumu göstermeniz gerekiyorsa bu yararlı olabilir: ana işlemi eşzamansız (veya başka bir iş parçacığında) çalıştırın ve "ne kadar kaldı? "zaman uyumsuz çağrı (veya iş parçacığı) tamamlanana kadar her zaman kontrol edin.

Yalıtım seviyesinin mümkün olduğunca gevşek olarak ayarlanması, kilitleme sorunları nedeniyle ana işlemin arkasında kalmadan makul bir süre içinde geri dönmesi gerektiği anlamına gelir. Bu, döndürülen değerin elbette biraz yanlış olduğu anlamına gelebilir, ancak basit bir ilerleme ölçer olarak bu hiç önemli olmamalıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.