在SQL Server中,如何快速刪除大表中的數據呢? 回答這個問題前,咱們必須弄清楚上下文環境和以及結合實際、具體的需求,不一樣場景有不一樣的應對方法。sql
1: 整張表的數據所有刪除數據庫
若是是整張表的數據所有清空、刪除,這種場景卻是很是簡單,TRUNCATE TABLE確定是最快的。 反而用DELETE處理的話,就是一個糟糕的策略。app
2: 大表中刪除一部分數據性能
對於場景一、很是簡單,可是不少實際業務場景,並不能使用TRUNCATE這種方法,實際狀況可能只是刪除表中的一部分數據或者進行數據歸檔後的刪除。假設咱們遇到的表爲TEST,須要刪除TEST表中的部分數據。那麼首先咱們須要對錶的數據量和被刪除的數據量作一個彙總統計,具體,咱們應該採用下面方法:測試
· 檢查表的數據量,以及要刪除的數據量。而後計算刪除的比例,this
sp_spaceused 'dbo.TEST'; spa
SELECT COUNT(*) AS DELETE_RCD WHERE TEST WHERE ......<刪除條件>日誌
2.1 刪除大表中絕大部分的數據,可是這個絕大部分怎麼定義很差量化,因此咱們這裏就量化爲60%。若是刪除的數據比例超過60%,就採用下面方法:code
1: 新建表TEST_TMPorm
2: 將要保留的數據轉移到TEST_TMP
3: 將原表TEST重命名爲TEST_OLD, 而將TEST_TMP重命名爲TEST
4: 檢查相關的觸發器、約束,進行觸發器或約束的重命名
5: 覈對操做是否正確後,原表(TEST_OLD)要麼TRUANCATE後,再DROP掉。要麼保留一段時間,保險起見。
注:至於這個比例60%是怎麼來的。這個徹底是個經驗值,有簡單的測試,可是沒有很精確和科學的機率統計驗證。
另外,還要考慮業務狀況,若是一直有應用程序訪問這個表,其實這種方式也是比較麻煩的,由於涉及數據的一致性,業務中斷等等不少狀況。可是,若是程序較少訪問,或者在某個時間段沒有訪問,那麼徹底能夠採用這種方法。
2.2 刪除大表中部分數據,若是比例不超過60%
1:先刪除或禁用無關索引(無關索引,這裏指執行計劃不用到的索引,這裏是指對當前DELETE語句無用的索引)。由於DELETE操做屬於DML操做,並且大表的索引通常也很是大,大量DELETE將會對索引進行維護操做,產生大量額外的IO操做。
2:用小批量,分批次刪除(批量刪除比一次性刪除性能要快不少)。不要一次性刪除大量數據。一次性刪除大量記錄。會致使鎖的粒度範圍很大,而且鎖定的時間很是長,並且還可能產生阻塞,嚴重影響業務等等。並且數據庫的事務日誌變得很是大。執行的時間變得超長,性能很是糟糕。
批量刪除時,到底一次性刪除多少數量的記錄數,SQL效率最高呢? 這個真沒有什麼規則計算,我的測試對比過,一次刪除10000或100000,沒有發現什麼特別規律。(有些你發現的「規律」,換個案例,發現不同的結果,這個跟環境有關,有時候多是一個經驗值)。不過通常用10000,在實際操做過程,我的建議能夠經過作幾回實驗對比後,選擇一個合適的值便可。
案例1:
DECLARE @delete_rows INT;
DECLARE @delete_sum_rows INT =0;
DECLARE @row_count INT=100000
WHILE 1 = 1
BEGIN
DELETE TOP ( @row_count )
FROM dbo.[EmployeeDayData]
WHERE WorkDate < CONVERT(DATETIME, '2012-01-01 00:00:00',120);
SELECT @delete_rows = @@ROWCOUNT;
SET @delete_sum_rows +=@delete_rows
IF @delete_rows = 0
BREAK;
END;
SELECT @delete_sum_rows;
案例2:
DECLARE @r INT;
DECLARE @Delete_ROWS BIGINT;
SET @r = 1;
SET @Delete_ROWS =0
WHILE @r > 0
BEGIN
BEGIN TRANSACTION;
DELETE TOP (10000) -- this will change
YourSQLDba..YdYarnMatch
WHERE Remark='今日未入' and Operation_Date<CONVERT(datetime, '2019-05-30',120);
SET @r = @@ROWCOUNT;
SET @Delete_ROWS += @r;
COMMIT TRANSACTION;
PRINT(@Delete_ROWS);
END
該表有下面兩個索引
USE [YourSQLDba]
GO
IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[YdYarnMatch]') AND name = N'IX_YdYarnMatch_N2')
DROP INDEX [IX_YdYarnMatch_N2] ON [dbo].[YdYarnMatch] WITH ( ONLINE = OFF )
GO
USE [YourSQLDba]
GO
CREATE NONCLUSTERED INDEX [IX_YdYarnMatch_N2] ON [dbo].[YdYarnMatch]
(
[Job_No] ASC,
[GK_No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
USE [YourSQLDba]
GO
IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[YdYarnMatch]') AND name = N'IX_YdYarnMatch_N1')
DROP INDEX [IX_YdYarnMatch_N1] ON [dbo].[YdYarnMatch] WITH ( ONLINE = OFF )
GO
USE [YourSQLDba]
GO
CREATE NONCLUSTERED INDEX [IX_YdYarnMatch_N1] ON [dbo].[YdYarnMatch]
(
[Operation_Date] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
重點:實踐證實,若是新建一個索引,可以避免批量刪除過程當中執行計劃走全表掃描,也能大大加快刪除的速度。我的對這個案例進行了測試、驗證。發現加上合適索引後,讓DELETE語句走Index Seek後,刪除效率確實大大提高。
刪除索引IX_YdYarnMatch_N2,保留索引IX_YdYarnMatch_N1,可是發現SQL執行計劃走全表掃描,執行SQL時,刪除很是慢
刪除索引IX_YdYarnMatch_N1,從新建立索引IX_YdYarnMatch_N1後,執行計劃走Index Seek,刪除效率大大提示。
CREATE NONCLUSTERED INDEX [IX_YdYarnMatch_N1] ON [dbo].[YdYarnMatch]
(
[Operation_Date] ASC ,
Remark
)
注意:此處索引名相同,可是索引對應的字段不同。
因此正確的作法是:
1:先刪除或禁用無關索引(對當前DELETE語句無用的索引),刪除前生成對應的SQL,以便完成數據刪除後,從新建立索引。注意,前提是在操做階段,這個操做不會影響應用。不然應從新考慮。
2:檢查測試當前SQL的執行計劃,可否建立合適的索引,加快DELETE操做。如上面例子所示
3:批量循環刪除記錄。
4:在ORACLE數據庫中,有些表的設置能夠減小對應DML操做的日誌生成量,可是SQL Server沒有這些功能,可是要及時關注或調整事務日誌的備份狀況。
若是咱們能將將數據庫的恢復模式設置爲SIMPLE,那麼能夠減小日誌備份引發的額外的IO開銷。可是不少生產環境不能切換用戶數據庫的恢復模式。
其實說了這麼多,SQL Server中大表快速刪除索引的方法就是將一次性刪除改爲分批刪除,逐次提交而已。其它的方式都是一些輔助方式而已。另外,若是你想親自作一些細節測試,建議參考博客https://sqlperformance.com/2013/03/io-subsystem/chunk-deletes