在SQL Server上优化删除

在sql server上Delete有时很慢,我经常需要优化它们以减less所需的时间。 我已经googleing了一下如何做到这一点的提示,我已经find了不同的build议。 我想知道你最喜欢和最有效的技术来驯服这只被删除的野兽,以及它们是如何工作的。

到现在:

  • 确保外键有索引

  • 确保在哪里条件索引

  • 使用WITH ROWLOCK

  • 销毁未使用的索引,删除,重build索引

该你了。

下面的文章, 快速sorting删除操作可能是你感兴趣的。

执行快速的SQL Server删除操作

解决scheme侧重于利用视图来简化为批处理删除操作生成的执行计划。 这是通过引用给定表一次而不是两次来实现的,这又减less了所需的I / O量。

我有更多的Oracle经验,但很可能同样适用于SQL Server:

  • 当删除大量的行时,发出一个表锁,所以数据库不必做大量的行锁
  • 如果您从中删除的表被其他表引用,请确保其他表在外键列上具有索引(否则数据库将对另一个表上的每个已删除行执行全表扫描以确保删除行不违反外键约束)

我想知道现在是垃圾收集数据库的时候了吗? 您将一行标记为删除,并在稍后扫描期间将服务器删除。 你不会希望每一次删除都是这样 – 因为有时候一行必须现在就行了,但是偶尔会很方便。

说实话,从表中删除一百万行与插入或更新一百万行一样严重。 这是问题的行集的大小,并没有太多的事情可以做。

我的build议:

  • 确保表中有主键和聚集索引(这对所有操作都是至关重要的)。
  • 确保聚集索引是这样的,如果要删除大块行,就会发生最小的页面重组。
  • 确保你的select标准是SARGable。
  • 确保当前所有的外键约束都是可信的。

回答总结通过2014-11-05

这个答案被标记为社区维基,因为这是一个不断变化的话题,有很多细微差别,但是总体上可能的答案很less。

第一个问题是你必须问自己什么情况下,你正在优化? 这通常是在数据库上使用单个用户的性能,或者在数据库上使用许多用户进行扩展。 有时答案是完全相反的。

对于单用户优化

  • 提示一个TABLELOCK
  • 删除删除中没有使用的索引,然后重build它们
  • 批量使用像SET ROWCOUNT 20000 (或其他什么,取决于日志空间)和循环(也许有一个WAITFOR DELAY ),直到你摆脱它( @@ROWCOUNT = 0
  • 如果删除大部分表格,只需创build一个新表格并删除旧表格
  • 分区行删除,然后删除parition。 [阅读更多…]

对于多用户优化

  • 提示行锁
  • 使用聚集索引
  • devise聚簇索引以减less页面重新组织,如果删除大块
  • 更新“is_deleted”列,然后在维护窗口中稍后进行实际删除

用于一般优化

  • 确保FK在源表上有索引
  • 确保WHERE子句有索引
  • 使用视图或派生表来标识要在WHERE子句中删除的行,而不是直接引用表。 [阅读更多…]

(如果索引是“未使用”的,为什么他们在那里?)

我过去使用的一个选项是分批完成这项工作。 粗糙的方法是使用SET ROWCOUNT 20000 (或其他)和循环(也许用一个WAITFOR DELAY ),直到你摆脱它(@@ ROWCOUNT = 0)。

这可能有助于减less对其他系统的影响。

问题是你没有足够的定义你的条件。 即什么是你优化?

例如,系统是否进行夜间维护,系统中没有用户? 你是否删除了大量的数据库?

如果脱机并删除一个很大的百分比,build立一个有数据保留的新表可能是有意义的,丢弃旧表并重命名。 如果删除一个很小的百分比,你可能希望按照日志空间允许的大批量进行批处理。 它完全取决于你的数据库,但是在重build期间删除索引可能会伤害或帮助 – 如果甚至可能由于“脱机”。

如果你在线,你删除的可能性与用户活动冲突的可能性是多less(用户活动主要是读,更新还是什么)? 或者,您是否试图优化用户体验或提高查询速度? 如果您要从其他用户经常更新的表中删除,则需要批量处理,但批量较小。 即使你像表锁一样强制执行隔离,如果你的delete语句需要一个小时,那也不会有太大的好处。

当你更好地定义你的条件时,你可以在这里select一个其他的答案。 我喜欢罗布·桑德斯(Rob Sanders)的post中的链接,

如果你有很多的外键表,从链的底部开始,并进行处理。 如果没有子logging级联删除(如果我有大量的子表,因为它会杀死性能,我不会打开),最后删除会更快,并阻止更less的事情。

分批删除。

如果你有不再使用的外键表(你会惊讶地发现生产数据结束了老表,没有人会摆脱),摆脱它们,或者至less打破FK / PK连接。 如果没有被使用的话,没有任何意义的表格可以用于logging。

不要删除 – 将logging标记为已删除,然后从所有查询中排除标记的logging。 这在数据库devise时最好设置。 很多人使用它,因为它也是最快的方式来取回logging意外删除。 但是在已经存在的系统中build立起很多工作。

我会再添加一个:

确保您的事务隔离级别和数据库选项设置得当。 如果您的SQL服务器设置为不使用行版本控制,或者您正在其他查询中使用隔离级别,您将等待删除行,则可能会在操作发生时为自己设置一些非常差的性能。

在非常大的表中,您有一组非常具体的删除条件,您也可以对表进行分区,切换出分区,然后处理删除操作。

SQLCAT团队一直在使用这种技术的真正大量的数据。 我在这里发现了一些引用,但我会尝试find更明确的东西。

我认为,删除这个性能的大陷阱就是sql在删除每一行之后,会更新这一行中任何一列的所有相关索引。 如何在批量删除之前删除所有索引?

有删除,然后有删除。 如果数据作为修剪作业的一部分而老化,那么希望能够通过聚簇键删除连续的行块。 如果你不得不从一个不连续的大容量数据表中老化数据,这是非常非常痛苦的。

如果确实UPDATE比DELETES更快,则可以添加一个名为DELETED的状态列,并在您的select中筛选它。 然后在晚上运行一个proc来做实际的删除操作。

你有激活参照完整性的外键吗? 你有触发器活动吗?

简化WHERE子句中的任何函数的使用! 例:

 DELETE FROM Claims WHERE dbo.YearMonthGet(DataFileYearMonth) = dbo.YearMonthGet(@DataFileYearMonth) 

WHERE子句的这种forms需要8分钟来删除125,837条logging。

YearMonthGet函数由inputdate和设置day = 1组成一个date与年份和月份。 这是为了确保我们根据年份和月份删除logging,而不是按月份。

我重写了WHERE子句:

 WHERE YEAR(DataFileYearMonth) = YEAR(@DataFileYearMonth) AND MONTH(DataFileYearMonth) = MONTH(@DataFileYearMonth) 

结果:删除需要大约38-44秒才能删除这些125,837条logging!