mysql和sql server的dml触发器均为语句级,update影响n行时只执行一次,inserted/deleted(sql server)或new/old(mysql)分别承载全部或单行数据,误用单值赋值会导致仅处理首行。

MySQL 和 SQL Server 的 DML 触发器默认是语句级的,UPDATE 语句影响 N 行,触发器只执行一次,INSERTED / DELETED 里装着全部变更行——不是“没触发”,而是你误以为它该逐行调用。
为什么触发器只执行一次却像没生效?
常见错觉源于对临时表的错误使用:比如在 MySQL 触发器里写 SELECT @id = id FROM INSERTED,或 SQL Server 里直接 SELECT TOP 1 id FROM inserted。这些写法看似取 ID,实则只拿到任意一行(甚至不确定哪一行),导致逻辑只处理了部分数据。
- 触发器内把
INSERTED当单值变量用,本质是集合误读 -
NEW在 MySQL 中是单行别名,不能代表多行;真正多行上下文只有INSERTED(SQL Server)或需改用INSERT ... SELECT+ 显式 JOIN - 日志或调试语句没加
COUNT(*),误判为“没触发”,实际是逻辑只跑了一次但没覆盖全集
MySQL 中如何让批量 UPDATE 正确处理所有行?
MySQL 没有 INSERTED 表,NEW 和 OLD 始终是单行引用。这意味着:原生触发器无法感知“本次 UPDATE 影响了多少行”,更无法遍历。所谓“正确处理所有行”,只能靠绕过触发器,或重构逻辑。
- 放弃在触发器里做多行判断,改用
INSERT INTO t SELECT ... ON DUPLICATE KEY UPDATE,用唯一键驱动更新,全程不走触发器 - 若必须保留触发器,只允许
BEFORE UPDATE且仅做字段计算(如NEW.updated_at = NOW()),禁止任何SELECT、UPDATE或循环 - 需要审计多行变更?用
BINLOG解析(Canal/Maxwell)监听真实变更集,比在触发器里硬凑NEW可靠得多
SQL Server 中怎么安全遍历 INSERTED 表?
SQL Server 的 INSERTED 是真集合,但直接用游标逐行处理是反模式——性能差、锁扩大、易死锁。应优先用集合操作。
- 用
JOIN inserted替代游标:例如UPDATE other SET balance = i.amount FROM other JOIN inserted i ON other.id = i.ref_id - 避免
WHERE id IN (SELECT id FROM inserted)这类写法,若inserted行数超万,可能触发嵌套循环退化 - 必须用游标时,限定
LOCAL FAST_FORWARD READ_ONLY,且禁止在循环体内再查表或发 RPC - 验证是否真处理了全部行:在触发器开头加
INSERT INTO debug_log SELECT GETDATE(), 'update', COUNT(*) FROM inserted
什么时候该彻底禁用触发器?
当你发现 SHOW PROCESSLIST 里线程卡在 Updating,或慢查询日志中同一条 UPDATE 反复出现、每次耗时稳定在几毫秒以上,基本可判定触发器已成瓶颈。此时优化无意义,因为 MySQL 触发器在批量场景下就是设计为逐行调用的,无法跳过、不可并行。
- 字段自动填充(如
updated_at)改用 MySQL 8.0+ 的生成列:ALTER TABLE t ADD COLUMN updated_at DATETIME AS (NOW()) STORED - 审计字段(如
created_by)必须由应用层显式传参,禁用@user_id或CURRENT_USER() - 统计类逻辑(如订单总数)移出事务,改用异步消费者或物化视图
- 临时应急可用
DISABLE TRIGGER trigger_name ON table_name,但上线前务必确认业务逻辑是否真依赖它
真正难的不是写触发器,而是在批量场景下意识到它根本不该存在——多数所谓“必须用触发器”的需求,其实只是还没把责任从数据库推回应用层而已。










