如何加快从大型数据库表中删除的速度?

Posted

技术标签:

【中文标题】如何加快从大型数据库表中删除的速度?【英文标题】:How do I speed up deletes from a large database table? 【发布时间】:2010-11-12 15:11:36 【问题描述】:

这是我要解决的问题:我最近完成了数据层重新设计,允许我跨多个分片对数据库进行负载平衡。为了保持分片平衡,我需要能够将数据从一个分片迁移到另一个分片,这涉及从分片 A 复制到分片 B,然后从分片 A 中删除记录。但是我有几个非常大的表,并且有许多外键指向它们,因此从表中删除一条记录可能需要一秒钟以上的时间。

在某些情况下,我需要从表中删除数百万条记录,但这需要太长时间才能实用。

禁用外键不是一种选择。删除大批量的行也不是一种选择,因为这是一个生产应用程序,大删除锁定了太多资源,导致失败。我正在使用 Sql Server,并且我知道分区表,但是分区限制(以及企业版的许可费用)是如此不切实际,以至于无法实现。

当我开始研究这个问题时,我认为最困难的部分是编写算法来计算如何删除从叶级到数据模型顶部的行,这样就不会违反外键约束大大地。但解决这个问题对我没有好处,因为删除需要一夜之间消失的记录需要数周时间。

我已经构建了一种将数据标记为虚拟删除的方法,因此就应用程序而言,数据已经消失了,但我仍在处理大型数据文件、大型备份和较慢的查询,因为桌子的大小。

有什么想法吗?我已经在这里阅读了较早的相关帖子,但没有发现任何有用的信息。

【问题讨论】:

为什么不能暂时禁用外键?您能否在维护期间不运行它并禁用对系统的访问? 是否可以选择完全不需要执行此复制过程?通过某种算法确保从 go 开始就在分片之间平均分配记录,而不是在创建记录后移动记录以平衡它们。 @AdaTheDev,对于新分片,这不是问题,但我最初的部署是备份-恢复,并且在每个副本上,将一半数据标记为虚拟删除。所以这两个碎片是巨大的。 @Mitch,分片的重点是使用更便宜的设备,所以我不会花钱解决问题。 @ck,我不确定 FK 是真正的问题。我认为花费最多的时间是聚集索引删除。 你能在这里发布一个执行计划吗?要获得一个,请在 SSMS 中加载查询时按 Control-L,然后截取屏幕截图(假设它都适合一个屏幕)。如果它太大,请查看您是否可以获得基于文本的计划副本。 我不想发布实际计划,但我可以很容易地描述它:一个聚集索引在最右边以 66% 删除,然后在 29 下以每个大约 1% 的速度寻找 FK . 【参考方案1】:

您可以创建新文件,复制除“已删除”行之外的所有行,然后交换表上的名称。最后,删除旧表。如果您要删除大部分记录,那么这实际上可能会更快。

【讨论】:

这可能是我可以尝试的,但我们谈论的是具有数千万条记录的表,聚集索引有几个 Gigs。它必须在正常的维护窗口内成为可能。【参考方案2】:

请看:Optimizing Delete on SQL Server

可能会对这篇 MS 支持文章感兴趣:How to resolve blocking problems that are caused by lock escalation in SQL Server:

将大批量操作分解为几个较小的操作。为了 例如,假设您运行以下 查询删除数百 来自审计的数千条旧记录 表,然后你发现它 导致锁升级阻塞 其他用户:

DELETE FROM LogMessages WHERE LogDate < '2/1/2002'    

通过删除这些记录 一次一百,你可以 大大减少数量 每个事务累积的锁 并防止锁升级。为了 示例:

SET ROWCOUNT 500
delete_more:
     DELETE FROM LogMessages WHERE LogDate < '2/1/2002'
IF @@ROWCOUNT > 0 GOTO delete_more
SET ROWCOUNT 0

通过使查询与 可能。 大扫描或大 书签查找的数量可能 增加锁定的机会 升级;此外,它增加了 死锁的机会,一般来说 对并发性产生不利影响 性能。

【讨论】:

这正是我要建议的。 @crokusek,添加到您的评论中,它不会影响此特定批次,因为 SET ROWCOUNT 500 使其成为该批次的 500。如果他们期望ROWCOUNT(不同于@@ROWCOUNT)是别的东西,那么随后的任何批次都可能会受到影响。 这简直太棒了。从来没有想过这样看待问题。我只能说哇!【参考方案3】:

如果您使用的是 SQL 2005 或 2008,也许使用“快照隔离”会对您有所帮助。它允许数据在进行底层数据更新操作处理时对用户保持可见,然后在数据提交后立即显示。即使您删除运行需要 30 分钟,您的应用程序也会在此期间保持在线状态。

这里是快照锁定的快速入门:

http://www.mssqltips.com/tip.asp?tip=1081

尽管您仍应尝试加快删除速度,以便尽可能快,但这可能会减轻一些负担。

【讨论】:

问题是我说的不是 30 分钟。我说的是数以千万计的行,每行需要我超过 1 秒的时间才能删除。加起来就是几个月。 每行删除 1 秒太多了。什么硬件? @TomTom:虽然这真的很慢,但并非不可想象。可能是硬件很慢,或者竞争激烈,或者删除是级联的,或者您要从中删除的表上有一个聚集索引导致大量物理数据移动,或者可能是一些删除触发器正忙于运行每一行。虽然您绝对可以做一些事情来尝试解决它,但速度如此缓慢可能是有原因的。 是的,但首先检查硬件绝不是一个坏主意。从你那里我会转向像删除查询计划这样的东西(很好 - 显示触发器)。【参考方案4】:
delete_more:
     DELETE TOP(500) FROM LogMessages WHERE LogDate < '2/1/2002'
IF @@ROWCOUNT > 0 GOTO delete_more

您可以按照 Mitch 的建议使用 SET ROWCOUNT 获得相同的结果,但 according to MSDN 在 SQL Server 的未来版本中将不支持 DELETE 和其他一些操作:

使用 SET ROWCOUNT 不会影响 DELETE、INSERT 和 UPDATE SQL Server 未来版本中的语句。避免使用 SET ROWCOUNT 在新的开发工作中使用 DELETE、INSERT 和 UPDATE 语句, 并计划修改当前使用它的应用程序。对于类似的 行为,使用 TOP 语法。有关详细信息,请参阅顶部 (Transact-SQL)。

【讨论】:

并且 RowCount 带有范围含义***.com/questions/5383761/…。例如,当它恢复时,你怎么知道它从零开始?【参考方案5】:

另一个建议是重命名表并添加一个状态列。当状态 = 1(已删除)时,您将不希望它显示。因此,您然后创建一个与原始表同名的视图,当状态为 null 或 = 0(取决于您如何实现它)时,该视图从表中选择。删除对用户来说是即时的,后台作业可以每 15 分钟运行一次,删除除了 dbas 以外的任何人都不知道的情况下运行的记录。

【讨论】:

【参考方案6】:

这里是您问题的解决方案。

DECLARE @RC AS INT
SET @RC = -1

WHILE @RC <> 0
BEGIN
    DELETE TOP(1000000) FROM [Archive_CBO_ODS].[CBO].[AckItem] WHERE [AckItemId] >= 300
    SET @RC = @@ROWCOUNT
    --SET @RC = 0
END

【讨论】:

【参考方案7】:

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

DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002'
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002'
END

【讨论】:

【参考方案8】:

如果表的相当大比例将匹配删除条件(接近或超过 50%),那么使用不会被删除的记录创建一个临时表“更便宜”(反转 WHERE标准),截断原始表,然后用打算保留的记录重新填充它。

DELETE FROM TABLE WHERE ROW_TO_DELETE = 'OK';
GO

-->

INSERT INTO #TABLE WHERE NOT ROW_TO_DELETE = 'OK';
TRUNCATE TABLE;
INSERT INTO TABLE (SELECT * FROM #TABLE);
GO

【讨论】:

以上是关于如何加快从大型数据库表中删除的速度?的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中如何加快大型集合的处理速度

mysql 7 +加入一个表,如何加快速度?

有哪些方法可以加快 Pytorch 中大型稀疏数组(约 100 万 x 100 万,密度约 0.0001)的数据加载速度?

如何删除大型表的嵌套循环连接

Kendo UI Dropdownlist 从大型数据源加载缓慢

删除大型 postgresql 数据库表中的重复行