没有子句的大型DELETE FROM <table>的加速方法


37

使用SQL Server 2005。

我正在执行巨大的DELETE FROM而没有where子句。它基本上等效于TRUNCATE TABLE语句-除了不允许使用TRUNCATE。问题是表很大-一千万行,而且要花一个多小时才能完成。有没有什么办法可以使其更快而没有:

  • 使用截断
  • 禁用或删除索引?

t日志已经在单独的磁盘上。

任何建议欢迎!


2
如果您将要做很多事情,请考虑对表进行分区
Gaius

1
因为存在引用表的FK约束,您不能使用TRUNCATE吗?
Nick Chammas

Answers:


39

您可以做的是批量删除,如下所示:

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

xxx代表50000

如果您要删除很高百分比的行,请对此进行修改...

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

3
@tuseau:每个删除都需要一些日志空间以防出错,以进行回滚。50k行删除比10m行删除占用更少的资源/空间。当然,日志备份仍在运行,并且会占用空间,但在服务器上进行小批量处理比处理大批备份更容易。
gbn

1
谢谢,批量删除有所帮助,我想这是最好的选择。
索2011年

2
@Phil Helmer:如果批量删除是在事务中,那么使用它是没有收益的。否则,每个日志写操作都较小,这就是简单的加载
gbn

1
进一步说明:批量删除有很大帮助,从1小时42分钟到3分钟要删除2000万行-但是请确保该表具有聚集索引!如果是堆,TOP子句会在执行计划中创建一个排序,该排序会否定任何改进。事后似乎很明显。
tuseau 2011年

2
@Noumenon:它确保@@ ROWCOUNT为1
gbn

21

您可以使用TOP子句轻松完成此操作:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END

大括号格式化您的代码
gbn

@gbn就是这样。这里仍然是101 010
bernd_k

7

如果您无法使用TRUNCATE,我同意将删除操作批量处理为可管理块的建议,并且我喜欢drop / create建议的独创性,但是我对您的问题中的以下评论感到好奇:

它基本上等效于TRUNCATE TABLE语句- 除非不允许使用TRUNCATE

我猜想这种限制的原因与直接截断表所需的安全性有关,并且它允许您截断与您关心的表不同的表。

假设是这种情况,我想知道是否创建了一个使用TRUNCATE TABLE并使用“ EXECUTE AS”的存储过程,而不是提供直接截断该表所必需的安全权限的可行选择。

希望这可以为您提供所需的速度,同时还可以解决您公司将帐户添加到db_ddladmin角色时可能遇到的安全问题。

以这种方式使用存储过程的另一个优点是,存储过程本身可以被锁定,从而仅允许特定帐户使用它。

如果由于某种原因这不是可接受的解决方案,并且您需要每天/每小时/等一次删除该表中的数据,我将请求创建一个SQL Agent作业以截断该表在每天的预定时间。

希望这可以帮助!


5

除了截断..只有批量删除可以帮助您。

您可以删除表并重新创建它,使其不受所有约束和索引的影响。在Management Studio中,您可以选择编写要删除和创建的表的脚本,因此它应该是一个简单的选项。但这仅在允许您执行DDL操作的情况下才有效,我认为这并不是一个选择。


因为该应用程序是为并发操作而设计的,所以更改结构(DDL)和使用截断不是选项...我想批量删除是最好的选择。不过谢谢
索2011年

1

由于此问题是如此重要,因此我将发布此代码,它确实帮助我了解了使用循环进行删除以及在循环内进行消息传递以跟踪进度的过程。

重复的问题中修改查询。记入@RLF作为查询基础。

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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.