Metode mempercepat DELETE FROM besar dari <tabel> tanpa klausa

37

Menggunakan SQL Server 2005.

Saya melakukan DELETE FROM besar tanpa klausa mana. Ini pada dasarnya setara dengan pernyataan TRUNCATE TABLE - kecuali saya tidak diizinkan untuk menggunakan TRUNCATE. Masalahnya adalah meja sangat besar - 10 juta baris, dan dibutuhkan lebih dari satu jam untuk menyelesaikannya. Apakah ada cara untuk membuatnya lebih cepat tanpa:

  • Menggunakan Truncate
  • Menonaktifkan atau menjatuhkan indeks?

Log-t sudah ada di disk terpisah.

Ada saran!

tuseau
sumber
2
Jika Anda akan sering melakukan ini, pertimbangkan mempartisi tabel
Gayus
1
Tidak bisakah Anda menggunakan TRUNCATE karena ada batasan FK yang mereferensikan tabel?
Nick Chammas

Jawaban:

39

Apa yang dapat Anda lakukan adalah penghapusan batch seperti ini:

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

Di mana xxx, katakanlah, 50000

Modifikasi ini, jika Anda ingin menghapus persentase baris yang sangat tinggi ...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable
gbn
sumber
3
@useau: setiap penghapusan membutuhkan ruang log jika terjadi kesalahan, untuk mengembalikan. Penghapusan baris 50k membutuhkan sumber daya / ruang lebih sedikit daripada penghapusan baris 10 m. Tentu saja, backup log masih berjalan dll dan mengambil ruang tetapi lebih mudah di server untuk banyak batch kecil daripada mucking yang besar.
gbn
1
Terima kasih, penghapusan batch sedikit membantu, saya kira itu pilihan terbaik.
tuseau
2
@Phil Helmer: jika penghapusan batch dalam transaksi maka tidak ada untungnya menggunakannya. Kalau tidak, masing-masing log log lebih kecil, yang lebih sederhana, memuat lebih mudah
gbn
1
Satu komentar lebih lanjut: penghapusan kumpulan sangat membantu, dan menghapus 20 juta baris dari 1 jam 42 menit hingga 3 menit - TAPI pastikan tabel memiliki indeks berkerumun! Jika itu tumpukan, klausa TOP menciptakan semacam dalam rencana eksekusi yang meniadakan perbaikan apa pun. Tampak jelas setelah itu.
tuseau
2
@Noumenon: Ini memastikan @@ ROWCOUNT adalah 1
gbn
21

Anda bisa menggunakan klausa TOP untuk menyelesaikan ini dengan mudah:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END
SQLRockstar
sumber
Kurung keriting memformat kode Anda
gbn
@ GBN Itu ada di SO. ini dia masih 101 010.
bernd_k
7

Saya setuju dengan saran untuk mengelompokkan penghapusan Anda ke dalam potongan yang dapat dikelola jika Anda tidak dapat menggunakan TRUNCATE, dan saya suka saran drop / buat orisinalitasnya, tetapi saya ingin tahu tentang komentar berikut dalam pertanyaan Anda:

Ini pada dasarnya setara dengan pernyataan TRUNCATE TABLE - kecuali saya tidak diizinkan untuk menggunakan TRUNCATE

Saya menduga alasan pembatasan ini berkaitan dengan keamanan yang perlu diberikan untuk langsung memotong tabel dan fakta bahwa itu akan memungkinkan Anda untuk memotong tabel selain yang Anda khawatirkan.

Dengan asumsi itu yang terjadi, saya bertanya-tanya apakah memiliki prosedur tersimpan dibuat yang menggunakan TRUNCATE TABLE dan menggunakan "EXECUTE AS" akan dianggap sebagai alternatif yang layak untuk memberikan hak keamanan yang diperlukan untuk memotong tabel secara langsung.

Mudah-mudahan, ini akan memberi Anda kecepatan yang Anda butuhkan sambil juga mengatasi masalah keamanan yang mungkin dimiliki perusahaan Anda dengan menambahkan akun Anda ke peran db_ddladmin.

Keuntungan lain menggunakan prosedur tersimpan dengan cara ini adalah prosedur tersimpan itu sendiri dapat dikunci sehingga hanya akun tertentu yang diizinkan untuk menggunakannya.

Jika karena alasan tertentu ini bukan solusi yang dapat diterima dan kebutuhan Anda untuk menghapus data dalam tabel ini adalah sesuatu yang perlu dilakukan sekali sehari / jam / dll, saya akan meminta agar pekerjaan SQL Agent dibuat untuk memotong tabel pada waktu yang dijadwalkan setiap hari.

Semoga ini membantu!

Jeff
sumber
5

Kecuali terpotong .. hanya menghapus dalam batch yang dapat membantu Anda.

Anda dapat menjatuhkan tabel dan membuatnya kembali, dengan semua kendala dan indeks, tentunya. Di Studio Manajemen, Anda memiliki opsi untuk membuat skrip tabel untuk dijatuhkan dan dibuat, jadi itu harus menjadi opsi sepele. Tapi ini hanya jika Anda diizinkan untuk melakukan tindakan DDL, yang saya lihat itu bukan pilihan.

Marian
sumber
Karena aplikasi ini dirancang untuk operasi bersamaan, mengubah struktur (DDL) dan menggunakan truncate bukanlah pilihan ... Saya kira batch delete adalah yang terbaik yang tersedia. Terimakasih Meskipun.
tuseau
1

Karena pertanyaan ini adalah referensi penting, saya memposting kode ini yang benar-benar membantu saya memahami penghapusan dengan loop dan juga pesan dalam satu lingkaran untuk melacak kemajuan.

Permintaan diubah dari pertanyaan duplikat ini . Kredit ke @RLF untuk basis permintaan.

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
Maks xm
sumber