触发器每次都编译吗?


22

我们正在对CPU利用率高的服务器进行故障排除。在发现查询并非真正引起查询之后,我们开始研究编译。

性能监视器显示少于50次编译/秒和少于15次重新编译/秒。

在运行XE会话以查找编译之后,我们每秒看到数千个编译。

该系统正在使用触发器来审核更改。大多数编译是由于触发器引起的。触发器参考sys.dm_tran_active_transactions。

我们的第一个想法是,在触发器中引用DMV会使它每次都编译,或者仅此特定DMV会导致它编译。因此,我开始测试该理论。它确实每次都编译,但是当它不引用DMV而是硬编码一个值时,我没有检查触发器是否在每次触发时都编译。每次触发时它仍在编译。放下触发器将停止编译。

  1. 我们在XE会话中使用sqlserver.query_pre_execution_showplan来跟踪编译。为什么与PerfMon计数器之间存在差异?
  2. 每次触发运行时,您都会收到一个编译事件是否正常?

复制脚本:

CREATE TABLE t1 (transaction_id int, Column2 varchar(100));
CREATE TABLE t2 (Column1 varchar(max), Column2 varchar(100));
GO

CREATE TRIGGER t2_ins
ON t2
AFTER INSERT
AS

INSERT INTO t1
SELECT (SELECT TOP 1 transaction_id FROM sys.dm_tran_active_transactions), Column2
FROM inserted;
GO

--Both of these show compilation events
INSERT INTO t2 VALUES ('row1', 'value1');
INSERT INTO t2 VALUES ('row2', 'value2');
GO

ALTER TRIGGER t2_ins
ON t2
AFTER INSERT
AS

INSERT INTO t1
SELECT 1000, Column2
FROM inserted;
GO

--Both of these show compilation events
INSERT INTO t2 VALUES ('row3', 'value3');
INSERT INTO t2 VALUES ('row4', 'value4');

DROP TRIGGER t2_ins;

--These do not show compilation events
INSERT INTO t2 VALUES ('row5', 'value5');
INSERT INTO t2 VALUES ('row6', 'value6');

DROP TABLE t1, t2;

Answers:


20

使用的XE事件使您错误地认为触发器实际上正在编译每个执行。有两个扩展事件query_pre_execution_showplan和query_post_compilation_showplan具有相似的描述,但相差一个重要的词:

query_pre_execution_showplan

在编译SQL语句后发生。此事件返回优化查询时生成的估计查询计划的XML表示形式。使用此事件可能会产生很大的性能开销,因此仅在短期内对故障进行故障排除或监视特定问题时才应使用此事件。

query_post_compilation_showplan

在编译SQL语句后发生。此事件返回在编译查询时生成的估计查询计划的XML表示形式。使用此事件可能会产生很大的性能开销,因此仅在短期内对故障进行故障排除或监视特定问题时才应使用此事件。

这些事件的描述并不完全相同,并且发生在与使用repro进行进一步测试不同的时间。使用更大的事件会话定义,很容易看到编译实际上在哪里进行。

在此处输入图片说明

在这里,您可以看到在绿色框中自动设置了准备好的计划的情况下,insert语句正在进行的第一次编译。如红色sp_cache_insert事件所示,触发器将在红色框中进行编译,并且计划将插入到缓存中。然后,在橙色框中,触发器执行将获得缓存命中,并为批处理中的第二条INSERT语句重用触发器计划,因此它不会编译INSERT命令的每次执行,并且该计划确实得到了重用,如通过sp_cache_hit事件看到的那样触发。

如果我们在第一次执行后再次分别运行两个INSERT语句,则触发器不会再次编译,如以下事件所示:

在此处输入图片说明

在这里,第一个语句在高速缓存中遇到了该语句的准备好的自动参数化版本的高速缓存命中,但是未提交已提交的即席批处理。触发器获得高速缓存命中,并且不会再次编译,如红色事件块所示。绿色事件块对于作为单独批处理运行的第二个INSERT语句重复此行为。但是,在每种情况下,您仍然会看到query_pre_execution_showplan事件触发,我只能将其归因于事件描述中优化对象编译对象之间的差异,但是触发器不会像这些事件系列所示的那样每次执行都进行编译。


如果您查看第一个屏幕截图,则未缓存的sql_batch_statistics事件在集合中,但仅在清除缓存且INSERT的自动参数化计划不在计划缓存中时,对于sql批处理的首次执行才触发。此后,不会再次触发uncached_sql_batch_statistics事件。
Jonathan Kehayias

query_post_compilation_showplan在触发器上仅显示了一些编译,但没有显示在其他事件上看到的大量编译。我们确实使用query_post_compilation_showplan找到了一些有趣的块。感谢您提供的信息,乔纳森!
塔拉·凯泽

13

不会。触发器不会总是重新编译。但是,简单的查询语句不会缓存其计划,因此总是会重新编译。

如果插入或删除的行数发生显着变化,则触发器会重新编译。请参阅:https : //technet.microsoft.com/zh-cn/library/ms181055.aspx

我不知道它们在XEvents中是否相同,但是在SQL Trace中,重新编译具有一个事件子类,该子类告诉您为什么重新编译它。在上面的同一链接中对此进行了解释。


1
我们正在查看的是编译而不是重新编译。我们明天将看服务器,并检查它是由于简单的查询语句还是由于行数。谢谢!
塔拉·凯泽
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.