如何在500,000+行表中不使用截断表时有效删除行

问题描述 投票:23回答:8

假设我们有30个列和500,000行的表Sales。我想删除表中的400,000(那些"toDelete='1'")。

但我有一些限制:

  • 该表是“经常”读/写的,我不想长时间“删除”需要很长时间并将表锁定太长时间
  • 我需要跳过事务日志(比如TRUNCATE),但在做"DELETE ... WHERE..."时(我需要设置一个条件),但还没有找到任何方法来做这个...

任何建议都欢迎改变一个

DELETE FROM Sales WHERE toDelete='1'

更多分区和可能的事务日志免费。

sql sql-server-2008 tsql sql-delete truncate
8个回答
35
投票

调用DELETE FROM TableName将在一个大型事务中执行整个删除操作。这很贵。

这是另一个批量删除行的选项:

deleteMore:
DELETE TOP(10000) Sales WHERE toDelete='1'
IF @@ROWCOUNT != 0
    goto deleteMore

10
投票

你想要的是批处理。

While (select Count(*) from sales where toDelete =1) >0
BEGIN
Delete from sales where SalesID in
(select top 1000 salesId from sales where toDelete = 1)
END

当然你可以试验哪个是批次使用的最佳值,我根据表使用500 - 50000。如果使用级联删除,则可能需要较小的数字,因为要删除这些子记录。


5
投票

我过去必须执行此操作的一种方法是使用存储过程或脚本来删除n条记录。重复直到完成。

DELETE TOP 1000 FROM Sales WHERE toDelete='1'

3
投票

你应该尝试给它一个ROWLOCK提示,这样它就不会锁定整个表。但是,如果删除很多行,则会发生锁定升级。

此外,请确保toDelete列上有非聚集的筛选索引(仅适用于1个值)。如果可能的话,使它成为一个列,而不是varchar(或它现在是什么)。

DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

最终,您可以尝试迭代表并以块的形式删除。

更新

虽然while循环和chunk删除是这里的新粉红色,但我也会抛出我的版本(结合我以前的答案):

SET ROWCOUNT 100
DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

WHILE @@rowcount > 0
BEGIN
  SET ROWCOUNT 100
  DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'  
END

3
投票

我对此功能的看法如下。这样就没有重复的代码,你可以管理你的块大小。

DECLARE @DeleteChunk INT = 10000
DECLARE @rowcount INT = 1

WHILE @rowcount > 0
BEGIN

  DELETE TOP (@DeleteChunk) FROM Sales WITH(ROWLOCK)

  SELECT @rowcount = @@RowCount
END

2
投票

我将在这里留下我的答案,因为我能够测试不同的批量删除和更新方法(我必须更新然后删除125 + mio行,服务器有16GB的RAM,Xeon E5-2680 @ 2.7GHz,SQL Server 2012)。

TL; DR:始终按主键更新/删除,绝不会通过任何其他条件。如果您不能直接使用PK,请创建临时表并使用PK值填充它,并使用该表更新/删除您的表。为此使用索引。

我从above(@Kevin Aenmey)的解决方案开始,但这种方法结果不合适,因为我的数据库是实时的,它每秒处理几百个事务并且涉及一些阻塞(所有的索引都有)有条件的领域,使用WITH(ROWLOCK)没有改变任何东西)。

所以,我添加了一个WAITFOR语句,它允许数据库处理其他事务。

deleteMore:
WAITFOR DELAY '00:00:01'
DELETE TOP(1000) FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3
IF @@ROWCOUNT != 0
    goto deleteMore

这种方法能够处理~1.6mio行/小时的更新和~0,2mio行/小时的删除。

转向临时表改变了很多东西。

deleteMore:
SELECT TOP 10000 Id /* Id is the PK */
  INTO #Temp 
  FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3 

DELETE MT
  FROM MyTable MT
  JOIN #Temp T ON T.Id = MT.Id 

/* you can use IN operator, it doesn't change anything
 DELETE FROM MyTable WHERE Id IN (SELECT Id FROM #Temp)

 */
IF @@ROWCOUNT > 0 BEGIN
    DROP TABLE #Temp
    WAITFOR DELAY '00:00:01'
    goto deleteMore
END ELSE BEGIN
    DROP TABLE #Temp
    PRINT 'This is the end, my friend'
END

该解决方案处理~25mio行/小时进行更新(快15倍)和~2.2mio行/小时进行删除(快11倍)。


1
投票

我用下面的内容删除了大约5000万条记录 -

BEGIN TRANSACTION     
     DeleteOperation:
     DELETE TOP (BatchSize)
     FROM  [database_name].[database_schema].[database_table] 

     IF @@ROWCOUNT > 0
     GOTO DeleteOperation
COMMIT TRANSACTION

请注意,保持BatchSize <5000的资源更便宜。


0
投票

我认为删除大量记录的最佳方法是通过Primary Key删除它。 (什么是Primary Key see here

因此,您必须生成包含要删除的整个行列表的tsql脚本,然后执行此脚本。

例如,下面的代码将生成该文件

GO
SET NOCOUNT ON

SELECT   'DELETE FROM  DATA_ACTION WHERE ID = ' + CAST(ID AS VARCHAR(50)) + ';' + CHAR(13) + CHAR(10) + 'GO'
FROM    DATA_ACTION
WHERE  YEAR(AtTime) = 2014

输出文件会有像这样的记录

DELETE FROM  DATA_ACTION WHERE ID = 123;
GO
DELETE FROM  DATA_ACTION WHERE ID = 124;
GO
DELETE FROM  DATA_ACTION WHERE ID = 125;
GO

现在你必须使用SQLCMD实用程序来执行这个脚本。

sqlcmd -S [Instance Name] -E -d [Database] -i [Script]

你可以在这里解释这种方法qazxsw poi

© www.soinside.com 2019 - 2024. All rights reserved.