我一直在尝试解决一个缓慢的触发问题,现在我已经通过反复试验,我仍然不知道最初的问题是什么。
我正在运行的查询如下:
UPDATE tblA
SET X = NULL
WHERE X IS NOT NULL AND Z = 0
它会更新大约30k行。
在tblA上导致问题的AFTER INSERT,UPDATE触发器部分是这样的:
IF EXISTS(SELECT 1
FROM inserted
LEFT JOIN deleted ON deleted.PK = inserted.PK
WHERE (inserted.Y IS NOT NULL AND deleted.Y IS NULL)
OR inserted.Y <> deleted.Y
BEGIN
-- The above condition is not met for my query so we would never get here
INSERT INTO tblB
(...)
SELECT
inserted.X,
...
FROM
inserted
LEFT JOIN deleted ON deleted.PK = inserted.PK
WHERE (inserted.Y IS NOT NULL AND deleted.Y IS NULL)
OR inserted.Y <> deleted.Y
END
我相信上面的IF EXISTS是为了阻止潜在的循环INSERT触发器在没有插入实际发生时触发,但这实际上不是tblB的问题,因为它只有一个触发器。
所以我把它改成了这个:
INSERT INTO tblB
(...)
SELECT
inserted.X,
...
FROM
inserted
LEFT JOIN deleted ON deleted.PK = inserted.PK
WHERE (inserted.Y IS NOT NULL AND deleted.Y IS NULL)
OR inserted.Y <> deleted.Y
现在更新查询时间已从&gt;下降1小时到30秒左右。
我预计它会花费相同的时间。为什么它更快?
更新:检查使用慢速触发器运行更新查询的执行计划
IF EXISTS检查的成本为0%,其中73%的成本转到另一个触发器语句,该语句将更改插入到审计表中。这本身似乎并不合理,因为该语句在很多连接中非常复杂,但我并不清楚为什么我改写IF EXISTS的改变有所不同。也许我的IF EXISTS检查干扰了审计表插入以某种方式减慢它们,但我不知道为什么新版本不会做同样的事情,因为它包含相同的SELECT。 [这笔费用的大部分用于急切的桌子。]
另外13%的查询成本花费在第三个触发器上,如果特定列值发生更改,则会更新tblA上的时间戳。这再次加入插入和删除,加上tblA。此更新语句对我的查询没有任何影响,因为列X更改不值得更新时间戳。 [此成本在tblA和插入之间的哈希匹配内部联接和聚集索引更新之间分配 - 似乎是合理的。]
添加更多的混淆:如果我禁用触发器,该触发器花费73%的时间但是在没有我的更改的情况下保留上面提到的旧触发器,我的查询仍然需要花费数小时才能运行。我没有尝试禁用时间戳触发器。
使用快速触发时查看查询计划,比率几乎完全相同,但整体时间更短。
答案 0 :(得分:1)
请调查执行计划,看看每次运行之间有什么区别。我猜SQL-server对exists(...)查询使用的执行计划与insert-select不同,因为它不必覆盖第一种情况下的所有列。如果存在令人困惑的索引或令人困惑的统计数据,优化可能会混淆并选择一个非常糟糕的计划。因此,在调查并保存执行计划后,尝试重新组织/重建所有索引并重新计算该表的统计信息。
问候,Rob
答案 1 :(得分:0)
我要改变的第一件事是:
WHERE (inserted.Y IS NOT NULL AND deleted.Y IS NULL)
到此:
WHERE (inserted.Y >'' AND deleted.Y IS NULL)
IS NULL导致索引搜索,其中&gt;''允许sql执行搜索并为您提供相同的结果集(取决于y是否为int,如果它是varchar,那么您可能会更改为&gt; = '')