sql server触发器中deleted是含全部被删行的临时表,错误用select @var=col from deleted仅取一行致逻辑错误;正确做法是将其视作普通表,用join、聚合或exists等集合操作处理。

触发器只“看到”第一行,是因为你用了单值变量读 DELETED
批量 DELETE FROM t WHERE id IN (1,2,3,100) 执行时,触发器确实只运行一次,但 DELETED 里装的是全部 100 行旧数据——不是一行。错误写法如:SELECT @id = id FROM DELETED,SQL Server 不报错,但 @id 只会取到其中某一行(顺序不可控),后续逻辑自然只基于这一行做判断或插入。
别用游标遍历 DELETED,那是性能黑洞
有人发现“只处理了一行”,立刻补个游标逐条读 DELETED,这在语法上能跑通,但代价极高:
- 锁持有时间翻倍,尤其高并发下易阻塞其他操作
- 日志体积暴涨,10 万行删除可能让事务日志增长数倍
- CPU 和 I/O 压力陡增,原本秒级的操作拖成分钟级
这不是“能用”,而是“不该用”。真要逐行逻辑,优先考虑是否该移到应用层或用异步队列解耦。
正确做法:把 DELETED 当成普通表 JOIN 或子查询
DELETED 是真实存在的内存临时表,支持 JOIN、EXISTS、GROUP BY、窗口函数等所有集合操作。例如:
INSERT INTO audit_log (table_name, op, row_count, deleted_at) SELECT 'orders', 'DELETE', COUNT(*), GETDATE() FROM DELETED;
再比如同步更新库存:
UPDATE stock SET qty = qty - d.qty FROM stock s INNER JOIN DELETED d ON s.product_id = d.product_id;
关键点:
- 永远不要对
DELETED做无WHERE的单行SELECT ... =赋值 - 需要校验每行是否满足条件?用
EXISTS (SELECT 1 FROM DELETED d WHERE d.id = t.id AND ...) - 要记录每行删除详情?直接
INSERT INTO log SELECT *, GETDATE() FROM DELETED
MySQL 用户注意:REPLACE 和 ON DUPLICATE KEY UPDATE 根本不触发 BEFORE INSERT
如果你在 MySQL 里用 REPLACE INTO orders 或 INSERT ... ON DUPLICATE KEY UPDATE 批量“覆盖插入”,哪怕写了 BEFORE INSERT 触发器,它也完全不会执行。这是最隐蔽的“失效”场景:
- 主键/唯一键冲突时,MySQL 8.0.19 之前只可能走
BEFORE UPDATE -
BEFORE INSERT被跳过,校验、默认值填充、审计日志全失效 - 验证方式:
SHOW TRIGGERS LIKE 'orders',确认Timing和Event是否匹配你实际执行的语句类型
真正可控的替代方案是显式 DELETE + INSERT,或改用 INSERT ... ON CONFLICT(PostgreSQL)或 MERGE(SQL Server)。
最常被忽略的一点:触发器是否真被调用了,不能靠“有没有报错”来判断;必须查日志表或加 INSERT INTO debug_log SELECT 'del', COUNT(*) FROM DELETED,亲眼看到数字是不是你预期的批量数。











