我有一个相对较小的数据库,可能有几十万行,我们有一个存储过程,旨在清除“经销商”(应用程序聚合根)及其所有相关数据。该数据库位于 v12 Azure SQL 实例上,当它访问一个特定的表时,它会消耗该实例 100% 的 DTU/CPU,并且需要很长时间才能完成。
我认为问题在于该表有大量外键,可能大约 30 个。查看执行计划,您可以看到它正在执行大量Nested Loop
索引扫描以查找相关行。在尝试从该特定表中删除行之前,所有这些行都已被删除,因此这些循环实际上毫无意义。
我的问题是,禁用这些依赖表上的外键约束,删除,然后重新启用约束会更有效吗?这样做会不会产生一些负面影响,除了在技术上可能会在禁用约束时插入一些坏数据这一事实。还是有更好的方法来完成我想要完成的事情?
更新 这是一个 v12 Azure SQL 数据库,它不是 VM 中完整的 SQL Server 2012。我没有批量删除任何删除,有问题的语句是更大的 SPROC 的一部分。我通过撕开存储过程并分别运行它的零碎部分来识别这个特定的表删除。当它到达这张桌子时,实际上需要 15 分钟才能完成。它将影响数百行的顺序,具体取决于我们正在清除的特定“经销商”的状态。
在这种情况下我们不能进行软删除,因为通常我们会清除这个经销商以便可以替换它(软删除会导致重复并且需要大量代码来解决)。我调查了其他类型的阻塞和等待状态,没有发现其他似乎对此产生负面影响的东西。正如我提到的,这个数据库相对较小,主要用于非常简单的 CRUD 操作。
执行计划的 XML 版本可以在这里找到:https ://gist.github.com/CodingGorilla/6cf7a87df9257d5f93e0d545af9839c2
正如您所注意到的,该计划显示了许多索引扫描[1],其中几个索引多次扫描,例如
Facilities.IX_CustomerID
. 这将是它旋转 CPU 资源的原因:这些索引将位于缓冲池中,因此需要很少的 IO,并且 CPU 正在反复咀嚼它们,可能是整个它们。如果这是来自一个简单的
DELETE
语句并且所有其他活动是执行外键约束的引擎,那么我将在不支持索引的情况下检查外键。例如,其中一个扫描[Facilities]" Index="[IX_CustomerID]
具有搜索谓词[DataMateWeb].[dbo].[Facilities].[SurfaceCleanerID]=[DataMateWeb].[dbo].[Chemical].[ChemicalID]
,这表明索引覆盖[Facilities].[SurfaceCleanerID]
不存在或由于某种原因不能使用。当您定义一个外键时,不会自动创建一个索引来配合它,这可能会让人们感到惊讶,这将解释对表/集群的扫描,而不是对索引的多次搜索。在这些索引是外键的支持索引的任何情况下,请确保它们被正确定义并且没有严重过时的统计信息。如果删除操作不是那么简单,那么确保在决定哪些行将被删除时使用的任何连接和子查询都得到适当的索引的支持,并且连接和过滤谓词的排列方式使得它们在可能的情况下是可搜索的。
如果上述情况失败(即,所有 FK 和任何其他过滤和连接活动在具有它们可能寻求的索引方面看起来都很好),我要抓住的下一根稻草是:如果您一次删除许多行(
DELETE WHERE IN (<a result that could be a long list>)
)然后我看到让 SQL Server 扫描索引而不是使用多次搜索。这感觉有点讨厌[2] 但是:通过尝试仅用一行来确认行为来检查这一点,如果这至少改变了一些扫描以寻找(并因此提高了性能),请考虑在需要的行中进行游标被删除并一次做一个。[1] 因为它们是聚集索引扫描,所以实际上是表扫描,对于大表来说意味着大量数据被触及。
[2] 因为它很讨厌。但如果它很讨厌并且有效,它仍然有效!
如果您没有禁用 FK 或使数据库脱机的选项,您可以通过等待(睡眠)批量删除以保持 CPU 关闭。这将需要更长的时间,但这可能不是问题。