如何在没有日志的SQL中删除表的大数据?

时间:2014-06-13 20:22:28

标签: sql-server sql-server-2008 sql-optimization

我有一个大数据表。 这张表中有1000万条记录。

此查询的最佳方式是什么

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

12 个答案:

答案 0 :(得分:65)

@ m-ali的回答是正确的,但请记住,如果您不在每个块之后提交事务并执行检查点,那么日志可能会增长很多。我就是这样做的,并将这篇文章http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes作为参考,包括性能测试和图表:

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 :(得分:38)

您还可以使用GO +执行相同查询的次数。

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

答案 2 :(得分:9)

M.Ali 的这种变化对我来说很好。它会删除一些,清除日志并重复。我正在看日志的增长,下降和重新开始。

WITH PERMISSION_SET = EXTERNAL_ACCESS

答案 3 :(得分:8)

@Francisco Goldenstein,只是一个小小的修正。设置变量后必须使用COMMIT,否则WHILE将只执行一次:

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

答案 4 :(得分:7)

如果您愿意(并且能够)实现分区,那么这是一种有效的技术,可以在几乎没有运行时开销的情况下删除大量数据。但是,一次性运动不符合成本效益。

答案 5 :(得分:4)

我能够在几分钟内从我的2100万行表中删除1900万行 。这是我的方法。

如果此表上有自动递增主键,则可以使用此主键。

  1. 获取大型表的主键的最小值,其中readTime&lt; DATEADD(月,-7,GETDATE())。 (在readTime上添加索引,如果尚未存在,则无论如何都将删除此索引以及步骤3中的表)。让我们将它存储在变量'min_primary'

  2. 插入所有具有主键&gt;的行min_primary进入临时表(如果没有行的内存表不大)。

  3. 放下大桌子。

  4. 重新创建表格。将所有行从登台表复制到主表。

  5. 删除临时表。

答案 6 :(得分:3)

您可以使用while循环删除小批量,如下所示:

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

答案 7 :(得分:2)

另一种用途:

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

可选;

如果启用了事务日志,请禁用事务日志。

ALTER DATABASE dbname SET RECOVERY SIMPLE;

答案 8 :(得分:1)

缩短语法

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

答案 9 :(得分:1)

如果您使用的是SQL Server 2016或更高版本,并且您的表基于要删除的列创建分区(例如Timestamp列),则可以使用此新命令按分区删除数据。

>

带有(分区({|} [,... n]))

这将仅删除所选分区中的数据,并且应该是从表的一部分删除数据的最有效方法,因为它不会创建事务日志,并且完成速度与常规截断一样快,但是没有全部从表中删除的数据。

缺点是,如果您的表未设置分区,那么您需要继续学习并以常规方式删除数据,然后使用分区重新创建表,以便将来可以这样做,这就是我所做的。我将分区的创建和删除添加到了插入过程本身中。我的表有5亿行,所以这是减少删除时间的唯一选择。

有关更多详细信息,请参见以下链接: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL server 2016 Truncate table with partitions

下面是我首先删除数据的步骤,然后才能重新创建带有所需数据分区的表。该查询将在指定的时间段内运行几天,直到删除数据。

: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()

答案 10 :(得分:0)

如果我说没有循环,我可以使用GOTO语句使用sql server删除大量记录。 例如。

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

像这样,您可以删除较小的删除大小的数据。

让我知道是否需要更多信息。

答案 11 :(得分:0)

如果你想删除一个有大量记录的表的记录但保留一些记录, 您可以将需要的记录保存在一个类似的表中,并截断主表,然后将保存的记录返回到主表中。