我有一个大型数据表格。这个表格中有一千万条记录。
查询时最佳的方式是什么?
Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())
我有一个大型数据表格。这个表格中有一千万条记录。
查询时最佳的方式是什么?
Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())
如果你想删除该表的所有行,最简单的方法是使用Truncate table命令,类似于:
TRUNCATE TABLE LargeTable
GO
截断表将仅清空表,您无法使用WHERE子句限制要删除的行数,并且不会触发任何触发器。
另一方面,如果您要删除的数据超过80-90%,例如您总共有1100万行,并且您想要删除1000万行,则另一种方法是将这100万行(要保留的记录)插入到另一个暂存表中。然后截断该大表并重新插入这100万行。
或者,如果具有此大表作为其基础表的权限/视图或其他对象不受删除此表的影响,则可以将这些相对较小数量的行转移到另一个表中,删除此表并创建具有相同模式的另一个表,并将这些行导入回此已经变小的表中。
我能想到的最后一个选项是将数据库的恢复模式更改为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
不要忘记将恢复模式改回完整模式,我认为你必须备份才能使其完全生效(更改或恢复模式)。
@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
COMMIT TRANSACTION
和 CHECKPOINT
,则日志仍然会增长。感谢明确解释这一点。 - gkoul@Deleted_Rows
与10000进行比较,否则由于无限删除小数据集而导致无限循环。因此,WHILE (@Deleted_Rows = 10000)
-一旦没有完整的“页面”可删除,它就会停止。在您的实现中,WHILE (@Deleted_Rows > 0)
,即使仅删除了一行,while循环也会再次执行,并且下一次执行可能还会找到一行或两行要删除-导致无限循环。 - dutoitnsWHILE
循环内部重复计算日期:dateadd(MONTH,-7,GETDATE())
。 - dutoitnsWHILE
循环的不同迭代之间可以删除的新记录。 - dutoitns您还可以使用GO +要执行相同查询的次数。
DELETE TOP (10000) [TARGETDATABASE].[SCHEMA].[TARGETTABLE]
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100
GO xx
应该在哪个版本的 MS SQL Server 中使用?我收到了一个“找不到存储过程''” 的错误。但是如果没有 GO
命令,它就可以正常工作。 - Abel@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
这个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
如果您愿意(并且有能力)实现分区,那么这是一种有效的技术,可在很少的运行时间开销下删除大量数据。虽然对于一次性操作来说不够划算。
更简洁的语法
select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
DELETE TOP (10000) LargeTable
WHERE readTime < dateadd(MONTH,-7,GETDATE())
END
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
以下是我在重新创建包含所需数据的分区表之前首先执行的删除数据的步骤。此查询将在指定时间窗口内运行数天,直到数据被删除。
: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()