Aar*_*ght 7 sql-server performance cascade sql-server-2008
我一直在分析一个与特别慢的删除操作相关的系统中反复发生的"错误报告"(性能问题).长话短说:CASCADE DELETE关键似乎主要是负责任的,我想知道(a)这是否有意义,以及(b)为什么会这样.
我们有一个架构,比如说,小部件,那些位于相关表和相关相关表的大图的根部,等等.要非常清楚,主动不鼓励从此表中删除; 这是"核选择",用户并没有相反的幻想.然而,有时候必须这样做.
架构看起来像这样:
Widgets
|
+--- Anvils [1:1]
| |
| +--- AnvilTestData [1:N]
|
+--- WidgetHistory (1:N)
|
+--- WidgetHistoryDetails (1:N)
Run Code Online (Sandbox Code Playgroud)
列定义如下所示:
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, ...)
Run Code Online (Sandbox Code Playgroud)
真的,没什么可怕的.A Widget可以是不同的类型,a Anvil是特殊类型,因此关系是1:1(或更准确地说是1:0..1).然后是大量的数据 - 可能随着时间的推移AnvilTestData每次Anvil收集数千行,处理硬度,腐蚀,精确重量,锤子兼容性,可用性问题以及卡通头的冲击测试.
然后每个人Widget都有各种类型交易的漫长而枯燥的历史 - 生产,库存移动,销售,缺陷调查,RMA,维修,客户投诉等.单个小部件可能有10-20k的详细信息,或者根本没有,取决于它的年龄.
所以,不出所料,这里的CASCADE DELETE各个层面都存在关系.如果Widget需要删除,则意味着某些内容出现了严重错误,我们需要删除现有的小部件的任何记录,包括其历史记录,测试数据等.再次,核选项.
关系都是索引,统计数据是最新的.普通查询很快.除了删除之外,系统往往会非常流畅地哼唱.
最后,由于各种原因,我们只允许一次删除一个小部件,因此删除语句如下所示:
DELETE FROM Widgets
WHERE WidgetID = @WidgetID
Run Code Online (Sandbox Code Playgroud)
非常简单,无害的看起来删除... 运行需要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
Run Code Online (Sandbox Code Playgroud)
这两个"优化"都带来了显着的加速,每个加速都会在执行时间内缩短近一分钟,因此最初的2分钟删除现在需要大约5-10秒 - 至少对于新的小部件而言,没有太多的历史或测试数据.
只是要绝对清晰,还有一个是CASCADE从WidgetHistory对WidgetHistoryDetails,其中扇出是最高的,我只能从删除的一个起源Widgets.
级联关系的进一步"扁平化"导致逐渐减少的戏剧性但仍然明显的加速,一旦删除所有级联删除较大的表并删除显式删除,删除新的小部件几乎是瞬时的.
我使用DBCC DROPCLEANBUFFERS和DBCC FREEPROCCACHE每次测试之前.我已经禁用了可能导致进一步减速的所有触发器(尽管这些触发器会出现在执行计划中).我也正在测试较旧的小部件,并注意到那里的显着加速; 过去需要花费5分钟的删除现在需要20-40秒.
现在,我是"SELECT is not broken"哲学的热心支持者,但除了压榨,令人难以置信的CASCADE DELETE关系低效之外,似乎没有任何合理的解释.
所以,我的问题是:
这是SQL Server中DRI的已知问题吗?(我似乎无法在谷歌或此处找到任何关于此类事物的引用;我怀疑答案是否定的.)
如果没有,是否有另一种解释我所看到的行为?
如果这是一个已知问题,为什么这是一个问题,我可以使用更好的解决方法吗?
SQL Server最好是基于集合的操作,而CASCADE删除本质上是基于记录的.
SQL Server与其他服务器不同,它尝试优化基于集合的直接操作,但是,它只能在一个级别上运行.它需要在上层表中删除记录以删除较低级别表中的记录.
换句话说,级联操作可以向上运行,而您的解决方案可以向下运行,这更加基于集合和高效.
这是一个示例模式:
CREATE TABLE t_g (id INT NOT NULL PRIMARY KEY)
CREATE TABLE t_p (id INT NOT NULL PRIMARY KEY, g INT NOT NULL, CONSTRAINT fk_p_g FOREIGN KEY (g) REFERENCES t_g ON DELETE CASCADE)
CREATE TABLE t_c (id INT NOT NULL PRIMARY KEY, p INT NOT NULL, CONSTRAINT fk_c_p FOREIGN KEY (p) REFERENCES t_p ON DELETE CASCADE)
CREATE INDEX ix_p_g ON t_p (g)
CREATE INDEX ix_c_p ON t_c (p)
Run Code Online (Sandbox Code Playgroud)
,这个查询:
DELETE
FROM t_g
WHERE id > 50000
Run Code Online (Sandbox Code Playgroud)
及其计划:
|--Sequence
|--Table Spool
| |--Clustered Index Delete(OBJECT:([test].[dbo].[t_g].[PK__t_g__176E4C6B]), WHERE:([test].[dbo].[t_g].[id] > (50000)))
|--Index Delete(OBJECT:([test].[dbo].[t_p].[ix_p_g]) WITH ORDERED PREFETCH)
| |--Sort(ORDER BY:([test].[dbo].[t_p].[g] ASC, [test].[dbo].[t_p].[id] ASC))
| |--Table Spool
| |--Clustered Index Delete(OBJECT:([test].[dbo].[t_p].[PK__t_p__195694DD]) WITH ORDERED PREFETCH)
| |--Sort(ORDER BY:([test].[dbo].[t_p].[id] ASC))
| |--Merge Join(Inner Join, MERGE:([test].[dbo].[t_g].[id])=([test].[dbo].[t_p].[g]), RESIDUAL:([test].[dbo].[t_p].[g]=[test].[dbo].[t_g].[id]))
| |--Table Spool
| |--Index Scan(OBJECT:([test].[dbo].[t_p].[ix_p_g]), ORDERED FORWARD)
|--Index Delete(OBJECT:([test].[dbo].[t_c].[ix_c_p]) WITH ORDERED PREFETCH)
|--Sort(ORDER BY:([test].[dbo].[t_c].[p] ASC, [test].[dbo].[t_c].[id] ASC))
|--Clustered Index Delete(OBJECT:([test].[dbo].[t_c].[PK__t_c__1C330188]) WITH ORDERED PREFETCH)
|--Table Spool
|--Sort(ORDER BY:([test].[dbo].[t_c].[id] ASC))
|--Hash Match(Inner Join, HASH:([test].[dbo].[t_p].[id])=([test].[dbo].[t_c].[p]))
|--Table Spool
|--Index Scan(OBJECT:([test].[dbo].[t_c].[ix_c_p]), ORDERED FORWARD)
Run Code Online (Sandbox Code Playgroud)
首先,SQL Server删除记录的t_g,然后加入与删除的记录t_p,并删除从后者,最后加入已删除的记录t_p与t_c和删减t_c.
在这种情况下,单个三表连接会更有效,这就是您使用解决方法所做的事情.
如果它让你感觉更好,Oracle不会以任何方式优化级联操作:NESTED LOOPS如果你忘记在引用列上创建索引,它们总是如此,上帝会帮助你.
| 归档时间: |
|
| 查看次数: |
4010 次 |
| 最近记录: |