触发器里别写多表JOIN查询,因其同步阻塞执行,一次三张大表关联即卡住整个INSERT;应改用异步消息或定时任务,必须实时查时须确保JOIN字段有索引且类型严格一致。

触发器里别写多表 JOIN 查询
SQL Server 触发器执行慢,八成是因为在 AFTER INSERT 或 BEFORE UPDATE 里直接做了 SELECT ... FROM t1 JOIN t2 JOIN t3。触发器是同步阻塞执行的,哪怕只查一次三张大表的关联,整个 INSERT 就卡住。
实操建议:
- 把触发器内复杂查询拆出来,改用异步消息(如 Service Broker)或定时任务补数据
- 如果必须实时查,确保所有
JOIN字段都有索引,且类型严格一致(比如user_id INT关联log.user_id BIGINT会强制类型转换,索引失效) - 用
SET STATISTICS IO ON+SET STATISTICS TIME ON跑一遍触发器内的语句,重点关注Scan count和logical reads—— 单次超过 10 万逻辑读基本就是瓶颈
批量操作时触发器被重复执行 N 次
执行 INSERT INTO t VALUES (), (), () 或 UPDATE t SET x=1 WHERE id IN (1,2,3),触发器不是“每条语句执行一次”,而是“每行执行一次”。10 万行更新 = 触发器逻辑跑 10 万次。
实操建议:
- 能用
UPDATE ... FROM或MERGE替代的,就别依赖触发器。SQL Server 2019 完全支持MERGE,可在一个语句里完成匹配、插入、更新、删除 - 日志类触发器最容易踩这个坑:不要为每条插入记录都
INSERT INTO audit_log,改用INSERT ... SELECT批量写入,或先写入临时表再定时归档 - 用
@@ROWCOUNT判断是否真有数据变更,避免空操作触发冗余逻辑
NEW/OLD 引用和函数调用要克制
SQL Server 中没有 NEW/OLD 的显式变量声明语法,但你在触发器里反复写 INSERTED.user_id 或对它调用 CONVERT(DATETIME, INSERTED.ts_str),就会触发重复解析和隐式计算。
实操建议:
- 先用局部变量存起来:
DECLARE @user_id INT = (SELECT TOP 1 user_id FROM INSERTED); - 避免在
WHERE或JOIN条件里对列做函数运算,比如WHERE YEAR(created_at) = 2024会让索引失效;改用范围查询:WHERE created_at >= '2024-01-01' AND created_at - 别在触发器里解析大 JSON 字段——SQL Server 2016+ 支持
ISJSON()和JSON_VALUE(),先判断结构再提取,避免JSON_QUERY()对 null 值报错
索引缺失让触发器查询雪上加霜
触发器内哪怕只查一行,如果 WHERE user_id = (SELECT user_id FROM INSERTED) 对应的表没建索引,就会全表扫描。而触发器常在高并发写入路径上,一个没索引的点查就能拖垮整个写入吞吐。
实操建议:
- 检查触发器里所有
SELECT的WHERE条件字段,用sys.dm_db_missing_index_details查缺索引建议 - 对高频触发的表,优先建覆盖索引:比如触发器常查
SELECT name, email FROM users WHERE id = @id,就建CREATE INDEX IX_users_id_cover ON users(id) INCLUDE(name, email) - 注意触发器自身不会触发统计信息自动更新,若基表数据变动剧烈,手动执行
UPDATE STATISTICS防止执行计划劣化


















