查触发器执行次数需用sys.dm_exec_trigger_stats联合sys.triggers获取trigger_name、table_name、execution_count及last_execution_time;其execution_count为自启动或重编译后累计值,嵌套调用亦计入,且不记录单次耗时;平均耗时不可靠,应追踪宿主dml语句;精准捕获触发时间点推荐扩展事件;查某表触发器直接查询sys.triggers并过滤系统对象。

查触发器执行次数:用 sys.dm_exec_trigger_stats + sys.triggers
SQL Server 不记录单次触发器耗时,但能告诉你它被调用了多少次——关键看 sys.dm_exec_trigger_stats 这个动态管理视图。它只缓存近期执行过的触发器,刚创建或重启后没被触发的不会出现。
- 必须
JOIN sys.triggers才能把object_id映射成表名和触发器名,否则光看数字没法定位 -
execution_count是自上次 SQL Server 启动或该触发器重编译后的累计值,不是实时滚动计数 - 结果为空?先确认触发器确实被
INSERT/UPDATE/DELETE触发过;再检查是否被is_ms_shipped = 0过滤掉了(系统触发器默认不显示) - 嵌套调用也会被计入——比如一个触发器内部又
UPDATE了另一张带触发器的表,两个触发器的execution_count都会加 1
常用查询:
SELECT t.name AS trigger_name, OBJECT_NAME(t.parent_id) AS table_name, s.execution_count, s.last_execution_time FROM sys.dm_exec_trigger_stats s JOIN sys.triggers t ON s.object_id = t.object_id WHERE s.execution_count > 0;
算平均耗时:别信 avg_duration_ms,它不准
sys.dm_exec_trigger_stats 里有 total_elapsed_time 和 execution_count,看起来能算平均耗时,但这个值实际意义很弱。
- 触发器逻辑被内联编译进宿主语句(如
UPDATE Orders),所有 CPU、I/O、时间都算在那条 DML 语句头上,sys.dm_exec_trigger_stats根本没有cpu_time或logical_reads字段 - 所谓“平均耗时”其实是整个 DML 语句的耗时均摊,不是触发器本身的真实开销
- 如果
execution_count突增但avg_duration_ms很低,大概率是高频小操作;如果数值高且波动大,得结合sys.dm_exec_requests看具体慢语句
真正要定位性能瓶颈,得查宿主语句:
SELECT sql_text.text, r.total_elapsed_time / 1000.0 AS sec, r.logical_reads
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) sql_text
WHERE r.command IN ('INSERT', 'UPDATE', 'DELETE')
AND r.total_elapsed_time > 500000;
捕获具体触发时间点:用扩展事件(XEvent),不是 SQL Profiler
当需要知道“谁、什么时候、哪条语句触发了哪个触发器”,就得上事件捕获。SQL Server 2016+ 推荐用扩展事件(XEvent),老环境才用 fn_trace_gettable。
- XEvent 可以精准捕获
sp_statement_completed或sql_batch_completed,并过滤出触发器相关上下文(object_type = 'TR') -
fn_trace_gettable只能读已停止的 trace 文件,运行中的 trace 不可见;且文件体积增长快,未设轮转易占满磁盘 - 触发器事件类型是
SP:Starting(EventClass=43)和SP:Completed(EventClass=45),但 ObjectType=8048 才表示触发器 - 注意嵌套与递归:一次业务
UPDATE可能引发多层触发,XEvent 会如实记录每一层,但需靠nest_level或session_id关联上下文
查某个表有哪些触发器:直接查 sys.triggers
想快速确认某张表挂了哪些触发器,不用翻 SSMS,一条 SQL 就够。
-
sys.triggers的parent_id对应表的object_id,用OBJECT_NAME(parent_id)就能拿到表名 - 加
WHERE t.is_ms_shipped = 0过滤掉系统触发器,避免干扰 - 区分类型:
t.type_desc返回AFTER_TRIGGER或INSTEAD_OF_TRIGGER,注意INSTEAD OF常用于视图 - 禁用/启用状态不在这个视图里体现,得查
sys.trigger_events或用OBJECTPROPERTY(t.object_id, 'ExecIsTriggerDisabled')
示例:
SELECT t.name AS trigger_name, t.type_desc, OBJECT_NAME(t.parent_id) AS table_name
FROM sys.triggers t
WHERE t.parent_id = OBJECT_ID('Orders')
AND t.is_ms_shipped = 0;
触发器统计数据天生有延迟和聚合特性,sys.dm_exec_trigger_stats 不持久、不精确、不隔离。真要监控长期趋势,得自己建日志表定期采样;真要定位单次慢触发,得从宿主 DML 入手,而不是盯着触发器本身看。










