触发器中禁止join、子查询和多表dml操作,仅允许单表主键查询且limit 1;优先用new/old取值;避免批量操作引发n次执行;须用explain analyze验证查询行数≤1000。

触发器里别写 SELECT JOIN 或子查询
触发器是行级同步执行的,每插入/更新一行就跑一遍里面的 SQL。一旦里面带 JOIN 三张表,或者嵌套 SELECT ... FROM t1 WHERE id IN (SELECT id FROM t2),等于每次操作都触发一次全表扫描或临时哈希连接——数据量过万,延迟立刻可见。
实操建议:
- 把多表关联逻辑彻底移出触发器,改用应用层查好再传入,或发消息到 Kafka/RabbitMQ 让后台服务异步补数据
- 真要查外部状态,只允许单表、主键查询,且必须加
LIMIT 1;比如SELECT balance FROM users WHERE id = NEW.user_id LIMIT 1 - 用
EXPLAIN ANALYZE单独跑一遍触发器内那条查询,看rows是否超过 1000 —— 超了就得砍
禁止在触发器里做 INSERT/UPDATE/DELETE 同一张表
MySQL 直接报错 Can't update table 'xxx' in stored function/trigger,PG 虽不报错但容易死锁或递归触发。常见于想“自动更新状态字段”或“补默认值”,结果绕不开硬限制。
实操建议:
- 用
BEFORE INSERT或BEFORE UPDATE直接改NEW.xxx值,比如NEW.updated_at := NOW(),不是发一条UPDATE语句 - PostgreSQL 中记得最后
RETURN NEW,否则修改不生效;MySQL 则直接赋值即可 - 想联动更新其他表?可以,但目标表必须有主键索引,且 WHERE 条件必须命中该索引,否则照样慢
避免 SELECT INTO 和隐式一致性读
SELECT col INTO var FROM t WHERE id = NEW.id 看似简单,但在 InnoDB 里会开启一致性读(consistent read),高并发下极易和主 SQL 冲突,导致锁等待甚至死锁。尤其当 t 就是当前被修改的表时,基本等于自己卡自己。
实操建议:
- 优先用
NEW.col或OLD.col取值,它们是内存快照,零开销 - 非查不可时,显式加
FOR UPDATE并确保只查一行,例如SELECT balance FROM accounts WHERE id = NEW.account_id FOR UPDATE - 测试时盯紧
SHOW ENGINE INNODB STATUS的TRANSACTIONS段,找lock_mode X locks rec but not gap waiting这类提示
批量操作时触发器被调用 N 次,不是 1 次
INSERT INTO orders VALUES (), (), () 插 1000 行,触发器就执行 1000 次。日志类、统计类逻辑最容易在这里崩:每行都 INSERT INTO audit_log,等于写 1000 次磁盘 IO。
实操建议:
- 审计类场景直接关掉行级触发器,改用 MySQL 的 binlog 解析或 PostgreSQL 的 logical replication + 外部消费者
- 必须实时更新汇总字段(如用户总消费)?用
BEFORE INSERT直接算:NEW.user_total := COALESCE(OLD.user_total, 0) + NEW.amount - PostgreSQL 可用
pg_trigger_depth() > 1防递归;MySQL 没这能力,得靠业务层加skip_trigger标记字段绕过
最常被忽略的一点:触发器性能问题往往不是单条语句慢,而是它放大了原本就存在的索引缺失、类型不匹配、JSON 解析滥用等问题。上线前务必用真实批量数据压测,而不是只测单行。










