触发器是否真的在运行?答案是需主动验证而非猜测:在触发器开头加raiserror('trg_start',0,1) with nowait实时观察消息窗口;查sys.triggers确认is_disabled和is_instead_of_trigger状态;执行计划不显示触发器,须用set statistics xml on搜索节点或通过xml_deadlock_report的中procname含trg_前缀来确证。

触发器是否真的在运行?先确认执行路径
很多“性能下降”问题其实源于触发器根本没被调用,或者只在特定条件下才触发。别靠日志或业务现象猜——SQL Server 不会主动告诉你它跳过了某个 BEFORE 或 AFTER 触发器。
实操建议:
- 在触发器开头加
RAISERROR('trg_start', 0, 1) WITH NOWAIT,配合 SSMS 的“消息”窗口实时观察;PRINT不可靠,它可能被缓冲延迟甚至丢弃 - 查系统视图确认状态:
SELECT name, is_disabled, is_instead_of_trigger FROM sys.triggers WHERE parent_id = OBJECT_ID('your_table_name');注意is_instead_of_trigger = 1会完全接管原操作,开销天然更高 - 如果触发器里写了
INSERT INTO debug_log,务必用COMMIT后再查——否则事务回滚时日志也跟着消失
执行计划里找不到触发器?对,它根本不出现
SSMS 的“包括实际的执行计划”不会显示任何触发器节点,哪怕你建了 5 个 AFTER UPDATE 触发器,图形界面里也只看到 Clustered Index Update。这不是你设置错了,是 SQL Server 设计如此:触发器逻辑属于语句执行前/后的附加动作,不在查询优化器规划范围内。
实操建议:
- 启用
SET STATISTICS XML ON,拿到 XML 执行计划后手动搜索<relop>,看是否有额外的扫描或哈希匹配节点——它们大概率来自触发器内部 SQL</relop> - 用
sys.dm_exec_trigger_stats查执行次数:SELECT object_name(object_id), execution_count, total_elapsed_time FROM sys.dm_exec_trigger_stats ORDER BY total_elapsed_time DESC;但注意:高执行次数 ≠ 当前慢的根源,只是说明它活跃 - 别信慢日志:触发器内耗时语句不会单独计入
sys.dm_exec_query_stats,必须切片验证——禁用触发器前后对比整条 DML 的elapsed_time
死锁和锁等待暴涨?重点看锁组合是否异常
触发器导致的死锁,90% 都藏在 xml_deadlock_report 的 <executionstack></executionstack> 里。如果你只看图形化死锁图,很容易漏掉 procname="trg_after_update_order" 这种关键帧。
实操建议:
- 打开死锁 XML,定位每个
<frame>的procname属性;若含trg_前缀或明确指向触发器名,就坐实了参与链 - 查
sys.dm_tran_locks:主 SQL 是UPDATE orders,却在同一request_session_id下发现对audit_log的X锁,而业务逻辑中从不直接写audit_log——这就是触发器在背后加锁的铁证 - 避免用
sys.dm_os_waiting_tasks定位:触发器没有独立session_id,它的锁全部挂在父会话下,你只能看到“谁在等”,看不到“谁加的”
Profiler 捕不到触发器耗时?默认配置就是盲区
SQL Server Profiler 默认只捕获主 DML 语句(如 INSERT),触发器内部的 SELECT、UPDATE 或存储过程调用全被过滤掉。你看到“执行 62ms”,实际可能有 58ms 耗在触发器里——这正是最常被误判的盲区。
实操建议:
- 新建 Profiler 跟踪 → “事件选择”页 → 勾选
SP:StmtCompleted(不是SP:Completed)→ 取消勾选“仅收集客户端 API 调用” - 若触发器调用存储过程,必须额外勾选
RPC:Completed,否则调用链断裂 - 更推荐用 Extended Events:
CREATE EVENT SESSION [TraceTriggers] ON SERVER ADD EVENT sqlserver.sp_statement_completed(WHERE object_type = 'TR');object_type = 'TR'是唯一能精准过滤触发器语句的条件
真正难处理的不是单次触发器慢,而是它把批量操作拆成逐行事务,让锁持有时间、WAL 日志量、主从延迟全变成非线性增长。一个 INSERT INTO t VALUES (1),(2),(3) 本该一次提交,有触发器就变成三次独立事务——这种开销不会出现在任何单条语句的执行计划里,只能靠并发压测和锁视图交叉验证。










