Günlük olmadan SQL'deki büyük tablo verileri nasıl silinir?


128

Büyük bir veri tablom var. Bu tabloda 10 milyon kayıt var.

Bu sorgu için en iyi yol nedir

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) Tüm satırları readTime> = dateadd (MONTH, -7, GETDATE ()) için başka bir tabloya almak için bir tür ETL yazmak ve ardından bir Truncate tablosu düzenlemek ve verileri ETL kullanarak geri koymak istemediğiniz sürece korkuyorum. , günlüğe yazmasını engelleyemezsiniz
TMNT2014

Günlük tutma, esnek işlemlere sahip olmanın ya hep ya hiç işlevidir. Bazı işlemler için bir günlüğün olmaması tam anlamıyla bir anlam ifade etmiyor ama diğerleri için değil, aksi takdirde günlük işe yaramaz.
Erik Philips

1
Saklamak istediğiniz verileri dışa aktarın, tabloyu kesin ve tekrar içe aktarın
Bohemian

Diğer bir seçenek, günlüğe kaydedilmemiş bir tablo değişkenini kullanmak olacaktır. Bu nedenle, readTime> = dateadd (MONTH, -7, GETDATE ()) verilerinizi bir tablo değişkeninde saklayın ve ardından orijinal tabloyu kesin ve verileri tablo değişkeninden geri kopyalayın. Bununla birlikte, bir şeyler ters giderse ve tablonun yanlışlıkla kesilmesi durumunda verilerin yedeğini tutarım. :) Ve her zaman daha küçük bir ortamda betiğinizi test edin.
TMNT2014

Yanıtlar:


203
  1. Bu tablodaki tüm satırları Siliyorsanız, en basit seçenek tabloyu Kesmektir.

    TRUNCATE TABLE LargeTable
    GO

    Truncate table basitçe tabloyu boşaltır, silinen satırları sınırlamak için WHERE cümlesini kullanamazsınız ve hiçbir tetikleyici tetiklenmez.

  2. Öte yandan, verilerin yüzde 80-90'ından fazlasını siliyorsanız, örneğin toplam 11 Milyon satırınız varsa ve 10 milyonu silmek istiyorsanız, bu 1 milyon satırı (saklamak istediğiniz kayıtları eklemek) ) başka bir aşama tablosuna. Bu Büyük tabloyu kesin ve bu 1 Milyon satırı geri ekleyin.

  3. Veya altta yatan tablo olarak bu büyük tabloya sahip olan izinler / görünümler veya diğer nesneler bu tabloyu bırakarak etkilenmezse, bu nispeten küçük miktarda satırları başka bir tabloya alabilir, bu tabloyu bırakabilir ve aynı şemayla başka bir tablo oluşturabilir ve bunları içe aktarabilirsiniz. bu eski Büyük tabloya geri döner.

  4. Aklıma gelen son bir seçenek, veritabanınızı değiştirmek Recovery Mode to SIMPLEve daha sonra bunun gibi bir süre döngüsü kullanarak daha küçük gruplar halinde satırları silmek.

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

ve Kurtarma modunu tekrar tam olarak değiştirmeyi unutmayın ve bence tamamen etkili hale getirmek için bir yedek almanız gerekiyor (değişiklik veya kurtarma modları).


14
Ayrıca, bir tabloyu keserseniz, herhangi bir FK'ye sahip olamayacağınızı da unutmayın.
HLGEM

1
Ancak verilerin% 80-90'ını sildiğinizden nasıl emin olabilirsiniz? Sadece silinmesi gereken değer aralığım olduğunu varsayalım. Ve birkaç masam var. Bu yüzden her birini kontrol etmeli ve yüzdeyi hesaplamalıyım ve eğer% 30 civarındaysa bu yöntemin çok etkili olmadığını tahmin ediyorum ... Bilinmeyen durumlar için en uygun çözümü bulmaya çalışıyorum.
Archont

7
@Archont optimal solution for unknown casebu rüya değil mi? Ne yazık ki her hastalığı tek bir hapla tedavi edemezsiniz; Farklı senaryolar için bazı olası çözümler önerdim. Maalesef burada şerit mermi yok.
M.Ali

5
4. seçeneği seçerken dikkat edilmesi gereken bir nokta: Tablonun nasıl kullanıldığına bağlı olarak, kilit artışını önlemek için bir seferde 5000'den az satırı silmek daha iyi bir seçenek olabilir .
Daniel

Silinecek kayıtların sayısı tabloda kalacak kayıtlardan çok daha büyükse, orijinal tabloyu bırakacak ve orijinal tabloyu bırakacak ve geçici tabloyu yeniden adlandıracak kayıtların geçici tablosuna basit seçimin çok daha hızlı olduğunu gördüm. Bir yerde kimlik kimliği yabancı anahtarı kullanmadığınız için.
Vladimir Bozic

96

@ m-ali yanıtı doğrudur, ancak her parçadan sonra işlemi gerçekleştirmezseniz ve bir kontrol noktası gerçekleştirmezseniz günlüklerin çok büyüyebileceğini unutmayın. Bunu nasıl yapacağım ve performans testleri ve grafiklerle birlikte http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes adlı bu makaleyi referans olarak alıyorum :

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Kullanılabilir disk alanının sınırlı olması durumunda kabul edilen yanıt bu olmalıdır. Olmadan COMMIT TRANSACTIONve CHECKPOINTgünlükler hala büyüyor. Bunu netleştirdiğiniz için teşekkürler.
gkoul

+1. Sadece @Deleted_Rows10000 ile karşılaştırmak isteyebileceğinizi veya küçük veri kümelerini süresiz olarak sildiği için sonsuz döngü ile sonuçlanabileceğinizi unutmayın. Yani WHILE (@Deleted_Rows = 10000)- silinecek tam bir veri "sayfası" kalmaz silinmez. Uygulamanızda, WHILE (@Deleted_Rows > 0)while döngüsü yalnızca bir satırı silse bile yeniden yürütülür ve sonraki yürütme de silinecek bir veya iki satır bulabilir - bu da sonsuz bir döngü ile sonuçlanır.
NS du Toit

@NSduToit, WHERE yan tümcesi en az 7 aylık kayıtları dikkate alıyor, bu nedenle siz silme işlemini gerçekleştirirken bu koşulu karşılayan yeni kayıtlar olmayacak.
Francisco Goldenstein

Tekrar tekrar dahilinde tarihini hesaplamak olarak @FranciscoGoldenstein Eh, sorguda kullanılan tarih her yineleme ile farklı olacaktır WHILEdöngü kendisi: dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Ayrıca, belki bundan başka kullanım durumları için - belki yeni veriler, WHILEdöngünün farklı yinelemeleri arasında silinebilecek yeni kayıtlarla sonuçlanacak temel tabloya eklenebilir .
NS du Toit

52

Aynı sorguyu kaç kez yürütmek istediğinizi GO + 'yı da kullanabilirsiniz.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

Bunu beğendim, benim için çalışıyor Yanlışlıkla aynı satırı bir tabloya 26 Milyon kez ekledim ve tüm oluşumlarını silmem gerekiyor, tek bir silme ifadesinde sunucuda bellek kalmadı, bu yüzden bu harika bir soru , silinecek satır kalmadığında orta döngü durur mu?
ScottC

2
@ScottC, bu bir döngü değildir, sadece sorguyu tekrarlar (toplu iş gibi) ve eğer satırlarınız biterse hiçbir şeyi silemez. Ama durmayacak. sildiğiniz satır kalmazsa (etkilenen 0 satır) gibi bir şey alırsınız.
Bunkerbuster

ah, evet, sorumu gönderdikten yaklaşık 5 dakika sonra, silme işlemim bittiğinden beri, bunun çok yardımcı olduğunu keşfettim!
ScottC

1
Bu sözdiziminin hangi MS SQL Sunucusundan GO xxçalışması gerekiyor? Ben olsun "saklı yordam bulunamadı ''" hatası. GOKomut olmadan da iyi çalışıyor.
Abel

3
Hmm, onu çalıştırabilirim gibi görünüyor ve gerçekten birden çok kez çalışıyor, ancak MS SQL Mgt Studio'da belirtilen hatayla kırmızı kıvrımlı çizgiyi gösteriyor (ancak F5-run çalışıyor)
Abel

11

@Francisco Goldenstein, sadece küçük bir düzeltme. Değişkeni ayarladıktan sonra COMMIT kullanılmalıdır, aksi takdirde WHILE yalnızca bir kez çalıştırılacaktır:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

M.Ali'nin bu varyasyonu benim için iyi çalışıyor. Bazılarını siler, günlüğü temizler ve tekrarlar. Günlüğün büyümesini, düşmesini ve yeniden başlamasını izliyorum.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

Bu çok faydalı oldu! # of rowsBir seferde silmek için parametresini ve ayrıca WHEREtümceyi parametrelendirmek için değiştirdim . Tıkır tıkır çalışıyor!
Shiva

7

Bölümlemeyi uygulamaya istekliyseniz (ve yapabiliyorsanız), bu, çok az çalışma süresi ek yükü ile büyük miktarda veriyi kaldırmak için etkili bir tekniktir. Yine de bir defalık bir egzersiz için uygun maliyetli değil.


4

21 milyon satırlık tablomdan 19 milyon satırı dakikalar içinde silebildim . İşte benim yaklaşımım.

Eğer bir varsa otomatik artan birincil anahtar bu masada, o zaman bu birincil anahtar yararlanabilir.

  1. ReadTime <dateadd (MONTH, -7, GETDATE ()) olan büyük tablonun birincil anahtarının minimum değerini alın. (ReadTime'da dizin ekleyin, zaten yoksa, bu dizin yine de 3. adımdaki tabloyla birlikte silinecektir.) Onu bir 'min_primary' değişkeninde saklayalım

  2. Birincil anahtara sahip tüm satırları> min_primary bir aşama tablosuna yerleştirin (satır sayısı büyük değilse bellek tablosu).

  3. Büyük masayı bırak.

  4. Masayı yeniden oluşturun. Tüm satırları aşama tablosundan ana tabloya kopyalayın.

  5. Aşama tablosunu bırakın.


3

Bir while döngüsü kullanarak küçük grupları silebilirsiniz, bunun gibi bir şey:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Başka bir kullanım:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

İsteğe bağlı;

İşlem günlüğü etkinse, işlem günlüklerini devre dışı bırakın.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Daha kısa sözdizimi

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

SQL server 2016 veya üstünü kullanıyorsanız ve tablonuzda silmeye çalıştığınız sütuna göre oluşturulmuş bölümler varsa (örneğin, Zaman Damgası sütunu), verileri bölümlere göre silmek için bu yeni komutu kullanabilirsiniz.

(BÖLÜMLEMELER ({|} [, ... n])) İLE TRUNCATE TABLE

Bu, yalnızca seçilen bölümlerdeki verileri siler ve işlem günlükleri oluşturmayacağından ve tüm verilerin silinmesine gerek kalmadan normal kesme kadar hızlı yapılacağından, tablonun bir bölümündeki verileri silmenin en etkili yolu olmalıdır. masadan.

Dezavantajı, tablonuz bölümle kurulmamışsa, eski moda gitmeniz ve verileri düzenli bir yaklaşımla silmeniz ve daha sonra bunu gelecekte yapabilmeniz için bölümlerle tabloyu yeniden oluşturmanız gerekir, ben de öyle yaptım. Bölüm oluşturma ve silme işlemlerini ekleme prosedürünün kendisine ekledim. 500 milyon satırlık bir masam vardı, bu yüzden silme süresini azaltmak için tek seçenek buydu.

Daha fazla ayrıntı için aşağıdaki bağlantılara bakın: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL server 2016 Bölümlerle tabloyu kes

Aşağıda, gerekli verilerin bulunduğu bölümlerle tabloyu yeniden oluşturmadan önce verileri silmek için ilk yaptığım şey. Bu sorgu, veriler silinene kadar belirtilen zaman aralığında günlerce çalışacaktır.

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Döngü olmadan dersem, GOTOsql server kullanarak büyük miktarda kayıt silmek için deyimi kullanabilirim . eksa.

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

bu şekilde daha küçük boyutta silme ile büyük miktarda veriyi silebilirsiniz.

daha fazla bilgi gerekiyorsa bana bildirin.

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.