触发器性能差主因是同步执行多表JOIN、重复解析NEW/OLD、批量操作行级触发及索引缺失;应改异步处理、预存变量、建合适索引并监控真实调用频次。

触发器里别写 SELECT 多表关联查询
触发器执行慢,八成是因为在 AFTER INSERT 或 BEFORE UPDATE 里直接查了多张大表。数据库执行触发器是同步阻塞的,哪怕只查一次 JOIN 三张千万级表,整个 INSERT 就卡住。
实操建议:
- 把触发器里的
SELECT ... FROM t1 JOIN t2 JOIN t3拆出来,改用异步消息(如 Kafka、RabbitMQ)或定时任务补数据 - 如果必须实时查,确保所有
JOIN字段都有索引,且类型完全一致(比如user_id INT关联log.user_id BIGINT会强制类型转换,索引失效) - 用
EXPLAIN ANALYZE跑一遍触发器内的查询语句,重点关注Seq Scan和Hash Join的行数——超过 10 万行基本就是瓶颈
NEW 和 OLD 引用要克制
PostgreSQL/MySQL 触发器里频繁读取 NEW.column_name 看似安全,但一旦配合函数调用(比如 to_timestamp(NEW.ts_str) 或 jsonb_extract_path_text(NEW.payload, 'user', 'id')),就可能触发隐式计算和重复解析。
实操建议:
- 避免在循环或条件判断里反复取同一个
NEW.xxx,先赋值给局部变量(PostgreSQL 支持DECLARE v_id INT := NEW.id;) - MySQL 的
BEFORE触发器中,NEW可写;但 PostgreSQL 中对NEW赋值需用RETURN NEW显式返回,否则修改无效 - 别在触发器里解析大 JSON 字段——提取前先用
jsonb_typeof(NEW.data)判断结构,避免对null或字符串调用jsonb_array_elements报错
批量操作时触发器被重复执行 N 次
执行 INSERT INTO t VALUES (...), (...), (...) 或 UPDATE t SET x=1 WHERE id IN (1,2,3),触发器不是“每条语句执行一次”,而是“每行执行一次”。10 万行更新 = 触发器逻辑跑 10 万次。
实操建议:
- 能用单条
UPDATE ... FROM或MERGE(SQL Server / PostgreSQL 15+)替代多行触发逻辑,就别依赖触发器 - PostgreSQL 可用
pg_trigger_depth()判断是否嵌套触发,防止递归调用;MySQL 没这能力,得靠业务层加标记字段绕过 - 日志类触发器最容易踩这个坑:不要为每条插入记录都
INSERT INTO audit_log,改用物化视图 + 定时归档,或写入 WAL 日志后异步消费
索引缺失让触发器查询雪上加霜
触发器内哪怕只查一行,如果 WHERE user_id = NEW.user_id 对应的表没建索引,就会全表扫描。而触发器常在高并发写入路径上,一个没索引的点查就能拖垮整个写入吞吐。
实操建议:
- 检查触发器里所有
SELECT的WHERE条件字段,用\d table_name(PostgreSQL)或SHOW INDEX FROM table_name(MySQL)确认索引存在且生效 - 复合索引要注意字段顺序:
WHERE status = 'active' AND created_at > '2024-01-01',索引应为(status, created_at),反过来效果差很多 - PostgreSQL 注意
text字段默认不支持 B-tree 索引全文匹配,要用varchar(n)或加表达式索引:CREATE INDEX ON logs ((lower(message)))
最麻烦的不是写不出快的触发器,而是你以为它只跑一次,结果它在每一行、每一次批量、每一个复制从库上都默默重复执行——监控 pg_stat_statements 或 performance_schema.events_statements_summary_by_digest 才看得清真实调用频次和耗时分布。










