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


127

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

什么是此查询的最佳方法

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

4
:)除非您愿意编写某种ETL以便将所有行readTime> = dateadd(MONTH,-7,GETDATE())放入另一个表,然后发出Truncate表并使用ETL将数据放回,您将无法阻止其写入日志
TMNT2014 2014年

日志记录是具有弹性事务的全部或全部功能。没有某些操作的日志,而没有其他操作的日志,从字面上看是没有意义的,否则该日志是无用的。
Erik Philips 2014年

1
导出要保留的数据,截断表,然后重新导入
Bohemian

另一种选择是使用未记录的表变量。因此,将您的readTime> = dateadd(MONTH,-7,GETDATE())数据存储在表变量中,然后截断原始表并从表变量中复制回数据。但是,我会备份数据,以防万一出了什么问题而表被无意间截断了。:)总是在较小的环境中对脚本进行测试。
TMNT2014 2014年

Answers:


203
  1. 如果要删除该表中的所有行,最简单的选择是截断表,例如

    TRUNCATE TABLE LargeTable
    GO

    截断表只会清空表,您不能使用WHERE子句来限制要删除的行,并且不会触发任何触发器。

  2. 另一方面,如果要删除80%到90%以上的数据,则说如果您总共有1,100万行并且要删除1000万行,则另一种方法是插入这100万行(要保留的记录)到另一个登台表。截断此大表,然后再插入这100万行。

  3. 或者,如果具有此大表作为其基础表的权限/视图或其他对象未因删除此表而受到影响,则可以将这些相对少量的行放入另一个表中,然后将该表删除并创建具有相同模式的另一个表并将其导入排回到这个前大表中。

  4. 我能想到的最后一个选择是更改数据库的内容Recovery Mode to SIMPLE,然后使用while循环这样的方式删除较小批量的行。

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

并且不要忘记将恢复模式更改回完全状态,我认为您必须备份以使其完全有效(更改或恢复模式)。


14
还请记住,如果截断表,则不能将任何FK与它关联。
HLGEM

1
但是如何确定要删除80-90%的数据?假设我只有应该删除的值范围。我有几张桌子。因此,我必须检查它们中的每一个并计算百分比,如果它在30%左右,我想这种方法不是很有效。。。我正在尝试为未知情况找到最佳解决方案。
Archont '16

7
@Archont optimal solution for unknown case那是梦想,不是吗?不幸的是,您不能用任何一种药来治愈所有疾病。我已经针对不同的情况提出了一些可能的解决方案。不幸的是,这里没有银弹。
阿里(M.Ali)'16

5
如果选择选项4,要注意的一件事:根据表的使用方式,一次删除少于5000行以避免锁升级可能是一个更好的选择。
丹尼尔(Daniel)

如果要删除的记录数要大得多,那么将保留在表中的记录数,我发现将记录保留在temp表中并简单地选择并删除原始表并重命名temp表的速度要快得多。鉴于您不在某处使用身份ID外键。
弗拉基米尔·博齐奇(Fladimir Bozic)

95

@ 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
如果可用磁盘空间有限,这应该是可接受的答案。没有它COMMIT TRANSACTIONCHECKPOINT日志还在增长。感谢您澄清这一点。
gkoul

+1。请注意,您可能想将其@Deleted_Rows与10000 进行比较,否则可能会因无限期删除少量数据集而导致无限循环。因此WHILE (@Deleted_Rows = 10000)-一旦没有完整的“页面”数据删除,它就会停止。在您的实现中,WHILE (@Deleted_Rows > 0)while循环即使只删除了一行也将再次执行,而下一次执行可能还会发现要删除的一行或两行-从而导致无限循环。
NS du Toit

@NSduToit WHERE子句正在考虑已存在至少7个月的记录,因此在执行删除操作时不会有满足该条件的新记录。
弗朗西斯科·戈登斯坦

@FranciscoGoldenstein嗯,由于您在WHILE循环本身中反复计算日期,因此每次迭代查询中使用的日期都会有所不同dateadd(MONTH,-7,GETDATE())
NS du Toit

@FranciscoGoldenstein另外,也许对于除此以外的其他用例-可能会将新数据添加到基础表中,这将导致可以在WHILE循环的不同迭代之间删除的新记录。
NS du Toit

52

您还可以使用GO +来执行同一查询多少次。

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

我喜欢这样,它对我有用,我不小心将同一行插入表中2600万次,并且需要删除所有出现的表,这在一个单独的delete语句中就耗尽了服务器上的内存,因此这是一个很好的问题,如果要删除的行用完了,它将停止中循环吗?
ScottC

2
@ScottC,这不是循环,它只是重复查询(类似批处理),如果用完了行,它将无法删除任何内容。但这不会停止。如果删除的行用完了,您将得到类似(0行受影响)的信息。
Bunkerbuster

嗯,是的,我发现我发布问题后大约5分钟,因为删除操作完成了,非常感谢!
ScottC

1
该语法GO xx应从哪个MS SQL Server运行?我收到“找不到存储过程”错误。如果没有该GO命令,它将正常运行。
亚伯

3
嗯,似乎我可以执行它,并且确实运行了多次,但是在MS SQL Mgt Studio中,它显示了带有所提到错误的红色花线(但是F5运行才起作用)
Abel

11

@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

10

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

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

这非常有用!我对其进行了修改,以参数化# of rows一次删除的内容以及该WHERE子句。奇迹般有效!
希瓦

7

如果您愿意(并能够)实现分区,那么这是一种有效的技术,可在运行时开销很小的情况下删除大量数据。但是,一次过的锻炼并不划算。


4

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

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

  1. 获取大表的主键的最小值,其中readTime <dateadd(MONTH,-7,GETDATE())。(在readTime上添加索引,如果尚不存在,则无论如何该索引都会与步骤3中的表一起删除。)让我们将其存储在变量“ min_primary”中

  2. 将所有具有主键> min_primary的行插入登台表(如果行数不大,则为内存表)。

  3. 放下大桌子。

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

  5. 删除登台表。


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

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;

2

语法较短

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

1

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

使用(分区({|} [,... n]))截断表

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

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

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

SQL Server 2016截断带有分区的表

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

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

0

如果我说没有循环,我可以使用GOTO语句使用SQL Server删除大量记录。exa。

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

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

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

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.