Bagaimana cara menghapus data besar tabel dalam SQL tanpa log?


127

Saya memiliki tabel data besar. Ada 10 juta catatan dalam tabel ini.

Apa cara terbaik untuk kueri ini

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

4
:) Saya takut kecuali Anda bersedia menulis semacam ETL untuk mendapatkan semua baris readTime> = dateadd (BULAN, -7, GETDATE ()) ke dalam tabel lain dan kemudian keluarkan tabel Truncate dan kembalikan data menggunakan ETL , Anda tidak akan bisa mencegahnya menulis ke log
TMNT2014

Logging adalah fungsi semua atau tidak sama sekali dari memiliki transaksi yang tangguh. Secara harfiah tidak masuk akal untuk tidak memiliki log untuk beberapa operasi tetapi tidak yang lain, jika tidak log tidak berguna.
Erik Philips

1
Ekspor data yang ingin Anda simpan, potong tabel, lalu impor kembali
Bohemian

Opsi lain akan menggunakan tablevariable yang tidak dicatat. Maka simpan data readTime> = dateadd (MONTH, -7, GETDATE ()) Anda dalam variabel tabel dan kemudian potong tabel asli dan salin kembali data dari variabel tabel. Namun saya akan menyimpan cadangan data jika terjadi kesalahan dan tabel akan terpotong secara tidak sengaja. :) Dan selalu lakukan uji coba skrip Anda pada lingkungan yang lebih rendah.
TMNT2014

Jawaban:


203
  1. Jika Anda menghapus Semua baris dalam tabel itu, pilihan paling sederhana adalah memotong tabel, kira-kira seperti itu

    TRUNCATE TABLE LargeTable
    GO

    Truncate table hanya akan mengosongkan tabel, Anda tidak dapat menggunakan klausa WHERE untuk membatasi baris yang dihapus dan tidak ada pemicu yang akan dipecat.

  2. Di sisi lain jika Anda menghapus lebih dari 80-90 Persen data, katakanlah jika Anda memiliki total 11 Juta baris dan Anda ingin menghapus 10 juta cara lain adalah dengan Memasukkan 1 juta baris ini (catatan yang ingin Anda pertahankan) ) ke meja pementasan lain. Pangkas tabel besar ini dan masukkan kembali 1 Juta baris ini.

  3. Atau jika izin / tampilan atau objek lain yang memiliki tabel besar ini sebagai tabel yang mendasarinya tidak terpengaruh dengan menjatuhkan tabel ini, Anda bisa mendapatkan jumlah baris yang relatif kecil ini ke tabel lain letakkan tabel ini dan buat tabel lain dengan skema yang sama dan impor ini baris kembali ke tabel ex-Large ini.

  4. Satu opsi terakhir yang bisa saya pikirkan adalah mengubah database Anda Recovery Mode to SIMPLEdan kemudian menghapus baris dalam batch yang lebih kecil menggunakan loop sementara seperti ini ..

    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

dan jangan lupa untuk mengubah mode Pemulihan kembali ke penuh dan saya pikir Anda harus mengambil cadangan untuk membuatnya sepenuhnya efektif (perubahan atau mode pemulihan).


14
Ingat juga bahwa jika Anda memotong sebuah tabel, Anda tidak dapat memiliki FK yang terbawa dengannya.
HLGEM

1
Tetapi bagaimana memastikan bahwa Anda menghapus 80-90% data? Anggap saja saya hanya memiliki rentang nilai yang harus dihapus. Dan saya punya beberapa tabel. Jadi saya harus memeriksa setiap dari mereka dan menghitung persentase, dan jika sekitar 30% saya kira metode ini tidak terlalu efektif ... Saya mencoba mencari solusi optimal untuk kasus yang tidak diketahui.
Archont

7
@ Archont, optimal solution for unknown caseitulah mimpinya bukan? Sayangnya Anda tidak dapat menyembuhkan setiap penyakit dengan satu pil; Saya telah menyarankan beberapa solusi yang mungkin untuk skenario yang berbeda. Sayangnya tidak ada peluru keras di sini.
M.Ali

5
Satu hal yang perlu diperhatikan jika memilih opsi 4: Tergantung pada bagaimana tabel digunakan, ini mungkin merupakan opsi yang lebih baik untuk menghapus kurang dari 5.000 baris sekaligus untuk menghindari eskalasi kunci .
Daniel

Jika jumlah catatan untuk dihapus jauh lebih besar daripada catatan yang akan tetap di tabel, saya menemukan bahwa pilih sederhana ke tabel temp dari catatan yang akan tetap masuk dan lepaskan tabel asli dan ganti nama tabel temp jauh lebih cepat. Mengingat Anda tidak menggunakan kunci asing ID identitas di suatu tempat.
Vladimir Bozic

95

@ m-ali jawaban benar tetapi juga perlu diingat bahwa log dapat tumbuh banyak jika Anda tidak melakukan transaksi setelah setiap chunk dan melakukan pos pemeriksaan. Ini adalah bagaimana saya akan melakukannya dan mengambil artikel ini http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes sebagai referensi, dengan tes kinerja dan grafik:

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
Ini harus menjadi jawaban yang diterima jika ruang disk yang tersedia terbatas. Tanpa COMMIT TRANSACTIONdan CHECKPOINTlog masih terus tumbuh. Terima kasih telah menjelaskan ini.
gkoul

+1. Perlu diketahui bahwa Anda mungkin ingin membandingkan @Deleted_Rowsdengan 10.000 atau Anda mungkin berakhir dengan loop tak terhingga karena menghapus data yang tidak terbatas. Jadi WHILE (@Deleted_Rows = 10000)- segera setelah tidak ada "halaman" data lengkap untuk menghapusnya akan berhenti. Dalam implementasi Anda,, WHILE (@Deleted_Rows > 0)loop-sementara akan mengeksekusi kembali bahkan jika itu hanya menghapus satu baris, dan eksekusi berikutnya mungkin juga menemukan satu atau dua baris untuk dihapus - menghasilkan loop tak terbatas.
NS du Toit

@NSduToit klausa WHERE sedang mempertimbangkan catatan yang setidaknya 7 bulan sehingga tidak akan ada catatan baru yang memenuhi kondisi itu saat Anda melakukan penghapusan.
Francisco Goldenstein

@FranciscoGoldenstein Nah, tanggal yang digunakan dalam query akan berbeda dengan masing-masing iterasi yang Anda berulang kali menghitung tanggal dalam WHILElingkaran itu sendiri: dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Juga, mungkin untuk kasus penggunaan lain selain yang ini - mungkin data baru akan ditambahkan ke tabel di bawahnya yang akan menghasilkan catatan baru yang dapat dihapus di antara berbagai iterasi WHILEloop.
NS du Toit

52

Anda juga dapat menggunakan GO + berapa kali Anda ingin menjalankan kueri yang sama.

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

Saya suka ini, ini berfungsi untuk saya. Saya tidak sengaja memasukkan baris yang sama ke tabel 26 Juta kali dan perlu menghapus semua kemunculannya, yang dalam satu pernyataan penghapusan kehabisan memori di server, jadi ini pertanyaan yang bagus. , apakah itu akan menghentikan pertengahan loop jika kehabisan baris untuk dihapus?
ScottC

2
@ Esccott, itu bukan loop, hanya mengulangi kueri (seperti batch) dan jika Anda kehabisan baris itu tidak dapat menghapus apa pun. Tapi itu tidak akan berhenti. Anda akan mendapatkan sesuatu seperti (0 baris) yang terpengaruh jika kehabisan baris yang Anda hapus.
Bunkerbuster

ah, ya saya menemukan bahwa sekitar 5 menit setelah saya memposting pertanyaan saya, karena penghapusan saya selesai, terima kasih ini sangat membantu!
ScottC

1
Dari MS SQL Server apa sintaks ini GO xxseharusnya berfungsi? Saya mendapatkan kesalahan "Tidak dapat menemukan prosedur tersimpan ''" . Tanpa GOperintah itu berfungsi dengan baik.
Abel

3
Hmm, sepertinya saya bisa menjalankannya, dan itu memang berjalan beberapa kali, tetapi dalam MS SQL Mgt Studio itu menunjukkan garis keriting merah dengan kesalahan yang disebutkan (tapi F5-run bekerja kemudian)
Abel

11

@Francisco Goldenstein, hanya koreksi kecil. COMMIT harus digunakan setelah Anda mengatur variabel, jika tidak, WHILE akan dijalankan sekali saja:

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

Variasi dari M.Ali ini bekerja dengan baik untuk saya. Menghapus beberapa, menghapus log dan mengulangi. Saya melihat log tumbuh, turun, dan mulai lagi.

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

Ini sangat berguna! Saya memodifikasinya untuk parameterisasi # of rowsuntuk menghapus sekaligus, dan juga WHEREklausa. Bekerja seperti pesona!
Shiva

7

Jika Anda bersedia (dan mampu) mengimplementasikan partisi, itu adalah teknik yang efektif untuk menghapus sejumlah besar data dengan sedikit overhead run-time. Namun, tidak hemat biaya untuk latihan sekali saja.


4

Saya dapat menghapus 19 juta baris dari meja saya yang terdiri dari 21 juta baris dalam hitungan menit . Ini pendekatan saya.

Jika Anda memiliki kunci primer peningkatan-otomatis pada tabel ini, maka Anda dapat menggunakan kunci utama ini.

  1. Dapatkan nilai minimum kunci utama dari tabel besar tempat readTime <dateadd (MONTH, -7, GETDATE ()). (Tambahkan indeks pada readTime, jika belum ada, indeks ini akan tetap dihapus bersama dengan tabel di langkah 3.). Mari kita simpan dalam variabel 'min_primary'

  2. Masukkan semua baris yang memiliki kunci utama> min_primary ke dalam tabel pementasan (tabel memori jika jumlah baris tidak besar).

  3. Jatuhkan meja besar.

  4. Buat ulang meja. Salin semua baris dari staging table ke tabel utama.

  5. Jatuhkan meja pementasan.


3

Anda dapat menghapus kumpulan kecil menggunakan loop sementara, sesuatu seperti ini:

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

Penggunaan lain:

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

Pilihan;

Jika log transaksi diaktifkan, nonaktifkan log transaksi.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Sintaks yang lebih pendek

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

1

Jika Anda menggunakan SQL server 2016 atau lebih tinggi dan jika tabel Anda memiliki partisi yang dibuat berdasarkan kolom yang Anda coba hapus (misalnya kolom Timestamp), maka Anda bisa menggunakan perintah baru ini untuk menghapus data dengan partisi.

TULANG MEJA DENGAN (PARTISI ({|} [, ... n]))

Ini akan menghapus data hanya di partisi yang dipilih dan harus menjadi cara paling efisien untuk menghapus data dari bagian tabel karena tidak akan membuat log transaksi dan akan dilakukan secepat pemotongan biasa tetapi tanpa menghapus semua data dari meja.

Kekurangannya adalah jika meja Anda tidak disetel dengan partisi, maka Anda harus menggunakan jadul dan menghapus data dengan pendekatan reguler dan kemudian membuat ulang tabel dengan partisi sehingga Anda bisa melakukan ini di masa depan, itulah yang saya lakukan. Saya menambahkan pembuatan partisi dan penghapusan ke dalam prosedur penyisipan itu sendiri. Saya punya meja dengan 500 juta baris jadi ini adalah satu-satunya pilihan untuk mengurangi waktu penghapusan.

Untuk detail lebih lanjut lihat tautan di bawah ini: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL server 2016 Tabel terpotong dengan partisi

Di bawah ini adalah apa yang saya lakukan pertama kali untuk menghapus data sebelum saya bisa membuat ulang tabel dengan partisi dengan data yang diperlukan di dalamnya. Kueri ini akan berjalan selama berhari-hari selama jendela waktu yang ditentukan hingga data dihapus.

: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

Jika saya katakan tanpa loop, saya bisa menggunakan GOTOpernyataan untuk menghapus sejumlah besar catatan menggunakan sql server. exa.

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

seperti cara ini Anda dapat menghapus sejumlah besar data dengan ukuran penghapusan yang lebih kecil.

beri tahu saya jika memerlukan informasi lebih lanjut.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.