我一直在分析我们的一个系统中与特别慢的删除操作相关的重复出现的“错误报告”(性能问题)。长话短说:似乎CASCADE DELETE
钥匙在很大程度上是负责任的,我想知道(a)这是否有意义,以及(b)为什么会这样。
我们有一个模式,比如说,小部件,它们位于相关表和相关表的大图的根部,等等。明确地说,不鼓励从该表中删除;这是“核选项”,用户对此并不抱任何幻想。然而,有时不得不这样做。
架构看起来像这样:
Widgets
|
+--- Anvils [1:1]
| |
| +--- AnvilTestData [1:N]
|
+--- WidgetHistory (1:N)
|
+--- WidgetHistoryDetails (1:N)
列定义如下所示:
Widgets (WidgetID int PK, WidgetName varchar(50))
Anvils (AnvilID int PK, WidgetID int FK/IX/UNIQUE, ...)
AnvilTestData (AnvilID int FK/IX, TestID int, ...Test Data...)
WidgetHistory (HistoryID int PK, WidgetID int FK/IX, HistoryDate datetime, ...)
WidgetHistoryDetails (HistoryID int FK/IX, DetailType smallint, ...)
没什么太可怕的,真的。AWidget
可以是不同的类型,anAnvil
是特殊类型,因此关系是 1:1(或更准确地说是 1:0..1)。然后是大量数据——随着时间的推移可能收集到数千行数据,涉及硬度、腐蚀、精确AnvilTestData
重量Anvil
、锤子兼容性、可用性问题以及卡通头的冲击测试。
然后每个Widget
人都有各种类型的交易的漫长而无聊的历史 - 生产,库存移动,销售,缺陷调查,RMA,维修,客户投诉等。单个小部件可能有 10-20k 详细信息,或者根本没有,取决于它的年龄。
因此,毫不奇怪,这里的CASCADE DELETE
每个层面都有关系。如果Widget
需要删除一个,则意味着出现了严重错误,我们需要删除该小部件的任何记录,包括其历史记录、测试数据等。再次,核选项。
关系都被索引,统计数据是最新的。普通查询很快。对于除删除之外的所有内容,系统往往会非常顺利地嗡嗡作响。
说到这里,最后,由于各种原因,我们一次只允许删除一个小部件,因此删除语句如下所示:
DELETE FROM Widgets
WHERE WidgetID = @WidgetID
非常简单,看起来无害的删除...对于没有数据的小部件,运行时间超过 2 分钟!
在苦苦完成执行计划之后,我终于能够挑选出AnvilTestData
和WidgetHistoryDetails
删除作为成本最高的子操作。所以我尝试关闭CASCADE
(但保留实际的 FK,只是将其设置为NO ACTION
)并将脚本重写为非常类似于以下内容:
DECLARE @AnvilID int
SELECT @AnvilID = AnvilID FROM Anvils WHERE WidgetID = @WidgetID
DELETE FROM AnvilTestData
WHERE AnvilID = @AnvilID
DELETE FROM WidgetHistory
WHERE HistoryID IN (
SELECT HistoryID
FROM WidgetHistory
WHERE WidgetID = @WidgetID)
DELETE FROM Widgets WHERE WidgetID = @WidgetID
这两个“优化”都导致了显着的加速,每一个都减少了近一分钟的执行时间,因此原来的 2 分钟删除现在大约需要 5-10 秒 - 至少对于新的小部件来说,没有太多历史记录或测试数据。
为了绝对清楚,仍然有一个CASCADE
from WidgetHistory
to WidgetHistoryDetails
,扇出最高的地方,我只删除了一个 originating from Widgets
。
级联关系的进一步“扁平化”导致了逐渐不那么显着但仍然明显的加速,以至于一旦删除了对较大表的所有级联删除并替换为显式删除,删除新小部件几乎是瞬时的。
我在每次测试之前使用DBCC DROPCLEANBUFFERS
and 。DBCC FREEPROCCACHE
我已经禁用了所有可能导致进一步减速的触发器(尽管这些触发器无论如何都会出现在执行计划中)。而且我也在针对旧的小部件进行测试,并注意到那里也有显着的加速;过去需要 5 分钟的删除现在需要 20-40 秒。
现在我是“SELECT ain't broken”哲学的热心支持者,但对于这种行为似乎没有任何合乎逻辑的解释,除了CASCADE DELETE
关系的粉碎、令人难以置信的低效率。
所以,我的问题是:
这是 SQL Server 中 DRI 的已知问题吗?(我似乎在 Google 或 SO 中找不到任何关于此类事情的参考资料;我怀疑答案是否定的。)
如果没有,我看到的行为是否有另一种解释?
如果这是一个已知问题,为什么会出现问题,我可以使用更好的解决方法吗?