如何在没有日志的情况下删除SQL中表的大数据?
Posted
技术标签:
【中文标题】如何在没有日志的情况下删除SQL中表的大数据?【英文标题】:How to delete large data of table in SQL without log? 【发布时间】:2014-06-13 20:22:27 【问题描述】:我有一个大数据表。 这张表有1000万条记录。
这个查询的最佳方式是什么
Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())
【问题讨论】:
:) 恐怕除非您愿意编写某种 ETL 以将所有行 readTime >= dateadd(MONTH,-7,GETDATE()) 放入另一个表,然后发出截断表并使用 ETL 将数据放回原处,您将无法阻止它写入日志 日志记录是具有弹性事务的全有或全无功能。对于某些操作没有日志而其他操作没有日志实际上是没有意义的,否则日志是无用的。 导出要保留的数据,截断表格,然后再导入 另一种选择是使用未记录的表变量。因此,将您的 readTime >= dateadd(MONTH,-7,GETDATE()) 数据存储在表变量中,然后截断原始表并从表变量中复制回数据。但是,我会保留数据备份,以防出现问题并且表格被无意截断。:) 并且总是在较小的环境中测试您的脚本。 How to efficiently delete rows while NOT using Truncate Table in a 500,000+ rows table的可能重复 【参考方案1】:如果您要删除该表中的所有行,最简单的选择是截断表,类似于
TRUNCATE TABLE LargeTable
GO
截断表只会清空表,你不能使用 WHERE 子句来限制被删除的行并且不会触发任何触发器。
另一方面,如果您要删除超过 80-90% 的数据,假设您总共有 1100 万行并且您想要删除 1000 万行,另一种方法是插入这 100 万行(记录您想保留)到另一个临时表。截断这个大表并插入这 100 万行。
或者,如果权限/视图或其他具有这个大表作为其基础表的对象不会受到删除此表的影响,您可以将这些相对少量的行放入另一个表中,删除此表并创建另一个表具有相同架构的表,并将这些行导入回这个 ex-Large 表中。
我能想到的最后一个选项是更改数据库的 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
别忘了将恢复模式改回完整模式,我认为您必须进行备份才能使其完全生效(更改或恢复模式)。
【讨论】:
还要记住,如果你截断一个表,你就不能有任何与之关联的 FK。 但是如何确保删除了 80-90% 的数据?假设我只有应该删除的值范围。我有几张桌子。所以我必须检查每一个并计算百分比,如果它在 30% 左右,我猜这种方法不是很有效......我正在尝试为未知情况找到最佳解决方案。 @Archontoptimal solution for unknown case
这就是梦想,不是吗?不幸的是,任何一种药丸都无法治愈所有疾病。我已经针对不同的场景提出了一些可能的解决方案。不幸的是,这里没有银弹。
选择选项 4 时要注意的一点:根据表的使用方式,一次删除少于 5000 行以避免lock escalation 可能是更好的选择。
如果要删除的记录数比保留在表中的记录大得多,我发现简单的选择到临时表中将保留的记录并删除原始表并重命名临时表要快得多。鉴于您没有在某处使用身份 ID 外键。【参考方案2】:
@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
,日志仍在增长。谢谢你说清楚。
+1。请注意,您可能希望将 @Deleted_Rows
与 10000 进行比较,否则您可能会因为无限期地删除少量数据而导致无限循环。所以WHILE (@Deleted_Rows = 10000)
- 一旦没有完整的“页面”数据删除它就会停止。在您的实现中,WHILE (@Deleted_Rows > 0)
,while 循环将再次执行,即使它只删除了一行,下一次执行也可能会找到一两行要删除 - 导致无限循环。
@NSduToit WHERE 子句正在考虑至少 7 个月前的记录,因此在您执行删除时不会有满足该条件的新记录。
@FranciscoGoldenstein 好吧,每次迭代时查询中使用的日期都会有所不同,因为您在 WHILE
循环本身内重复计算日期:dateadd(MONTH,-7,GETDATE())
。
@FranciscoGoldenstein 此外,也许对于除此之外的其他用例 - 可能会将新数据添加到基础表中,这将导致可以在 WHILE
循环的不同迭代之间删除的新记录。 【参考方案3】:
您还可以使用 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 万次,需要删除它的所有出现,在一个删除语句中内存不足服务器,所以这是一个很好的问题,如果它用完了要删除的行,它会停止中间循环吗? @ScottC,它不是一个循环,它只是重复查询(批处理),如果你用完了行,它就不能删除任何东西。但它不会停止。如果你删除的行用完了,你会得到类似(0 行受影响)的东西。 啊,是的,我在发布问题大约 5 分钟后发现,因为我的删除完成了,谢谢,这很有帮助! 这个语法GO xx
应该从什么MS SQL Server 工作?我收到 “找不到存储过程 ''” 错误。如果没有GO
命令,它可以正常工作。
嗯,好像我可以执行它,它确实运行了多次,但在 MS SQL Mgt Studio 中,它显示了带有上述错误的红色卷线(但 F5-run 工作)
【参考方案4】:
@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
【讨论】:
【参考方案5】: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
子句。像魅力一样工作!【参考方案6】:
如果您愿意(并且能够)实现分区,那么这是一种以很少的运行时开销删除大量数据的有效技术。不过,对于一次性练习来说并不划算。
【讨论】:
【参考方案7】:我能够在几分钟内从包含 2100 万行的表中删除 1900 万行。这是我的方法。
如果您在此表上有一个自增主键,那么您可以使用此主键。
获取 readTime
将所有具有主键 > min_primary 的行插入临时表(如果行数不大,则为内存表)。
放下大桌子。
重新创建表。将临时表中的所有行复制到主表。
删除临时表。
【讨论】:
不需要删除大表。进行截断也可以而且很快【参考方案8】:您可以使用 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
【讨论】:
【参考方案9】:更短的语法
select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
DELETE TOP (10000) LargeTable
WHERE readTime < dateadd(MONTH,-7,GETDATE())
END
【讨论】:
【参考方案10】:如果您使用的是 SQL Server 2016 或更高版本,并且您的表具有基于您要删除的列(例如 Timestamp 列)创建的分区,那么您可以使用这个新命令按分区删除数据。
TRUNCATE TABLE WITH (PARTITIONS ( | [ , ...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()
【讨论】:
仍然不适用于 FK,所以这是一个令人讨厌的限制 ..【参考方案11】:另一种用途:
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;
【讨论】:
【参考方案12】:如果我说没有循环,我可以使用GOTO
语句使用sql server 删除大量记录。
例如。
IsRepeat:
DELETE TOP (10000)
FROM <TableName>
IF @@ROWCOUNT > 0
GOTO IsRepeat
这样你可以用较小的删除量删除大量数据。
如果需要更多信息,请告诉我。
【讨论】:
【参考方案13】:如果要删除记录较多的表的记录,但保留部分记录, 您可以将所需的记录保存在类似的表中,并截断主表,然后将保存的记录返回到主表中。
【讨论】:
以上是关于如何在没有日志的情况下删除SQL中表的大数据?的主要内容,如果未能解决你的问题,请参考以下文章