【问题标题】:How can I prevent the transaction log from growing indefinitely when deleting lots of rows?删除大量行时如何防止事务日志无限增长?
【发布时间】:2019-11-06 14:05:34
【问题描述】:

我正在使用循环来删除多个块中的行。

WHILE EXISTS (SELECT 1 FROM dbo.Table WHERE LogTime < CAST('2018-11-05' AS 
  DATE))
  BEGIN
    BEGIN TRANSACTION T1
        DELETE TOP (1000) FROM dbo.Table 
        WHERE LogTime < CAST('2018-11-05' AS date);
    COMMIT;
END

我希望每个 While 循环周期都是一个以“COMMIT”结尾的事务,因此在每个循环周期后都会清理事务日志。

不幸的是,情况并非如此。在 15 分钟后的某个时刻,查询由于“事务日志已满”而崩溃。然后开始回滚,不仅回滚最后一个循环周期,而且回滚整个查询,包括所有其他循环周期。

【问题讨论】:

  • 日志不是这样工作的。在 FULL 模式下,日志记录会一直保留到日志备份为止。如果崩溃将所有内容回滚,则意味着您有 另一个 整体事务,因此即使在 SIMPLE 模式下也不会删除任何内容。 SQL Server 没有嵌套事务,因此外部事务是唯一重要的事务。提交内部事务不会做任何事情。 ROLLBACK 将回滚所有内容
  • 如果您经常批量删除,您应该使用表分区并使用TRUNCATE TABLE Imagination_Partitioned WITH (PARTITIONS(@partition)) 截断不需要的分区。自 SQL Server 2016 SP1 以来的所有版本(甚至 Express)都提供分区,并且鉴于 2016 是最旧的受支持版本,所有受支持的版本
  • 文章How to Partition SQL Server Tables and Truncate Partitions 解释了分区并包含一个有趣的示例,包括一个根据日期计算要截断哪些分区的方法
  • 您可以在事后创建分区,尽管随着数据的移动需要一段时间。分区方案取决于您的 要求 - 有多少行,您要删除多少数据以及多久删除一次?您还可以在具有相同架构的表之间切换分区(这只是元数据操作,几乎是即时的),这意味着您可以在截断分区之前将数据移动到历史表
  • 那时你还没有阅读我的评论。不,那不是真的。事实上,受支持的版本是 100% 错误的。 Partitions are available in all editions (even Express) since SQL Server 2016 SP1 and given that 2016 is the oldest supported version, all supported versions 其他随处可用的功能是“异国情调”,如列存储索引、压缩和内存表。您可能不需要删除任何数据,只需对其进行压缩或使用列存储

标签: sql-server transactions sql-delete


【解决方案1】:

我通过将数据库的恢复模式设置为“简单”而不是“完整”来解决此问题。这确保事务日志不会无限增长。但是您必须小心:这不是所有类型数据库的选项。在我们的例子中,我们正在存储日志信息,如果有数据丢失,可以很容易地重新导入。但是对于生产数据库,您应该使用“完整”恢复模式,而不是使用表分区。

【讨论】: