在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!