触发器导致update变慢是机制决定的:每行更新均同步执行触发器逻辑,10万行即执行10万次,含逐行select、函数计算、索引失效等开销,且无法批处理,禁用仅用于诊断而非解决。

更新带触发器的表慢,不是“可能”,而是机制决定的——它必须在事务内同步执行额外逻辑,且每行都触发一次。
触发器让UPDATE变成N次嵌套执行
执行 UPDATE t SET x = 1 WHERE id IN (1,2,3,4,5),哪怕只改5行,触发器也会被调用5次。如果触发器里有 INSERT INTO log 或 SELECT COUNT(*) FROM related,这些语句就重复跑5遍。10万行更新 = 触发器逻辑执行10万次,不是1次。
- SQL Server / PostgreSQL:
AFTER UPDATE中的语句对inserted或NEW表逐行生效,无法自动批处理 - MySQL:没有
inserted表,但NEW是逐行上下文,所有子查询都在行级作用域里重算 - 别信“只慢一点点”——高并发下锁等待、日志写入、执行计划重编译会指数级放大延迟
触发器里的SELECT最容易全表扫描
你看到主语句的 EXPLAIN 显示 type=eq_ref,不代表快;真正卡住的是触发器里那句 SELECT name FROM users WHERE id = NEW.user_id。这句不会出现在主 EXPLAIN 里,但每次都会真实执行。
-
WHERE UPPER(email) = UPPER(NEW.email)→ 索引失效,type=ALL -
WHERE user_id IN (SELECT id FROM temp_list)→ MySQL 8.0 前不支持对临时结果加索引提示 -
SELECT * FROM orders WHERE user_id = NEW.user_id AND status = 'paid'→ 如果orders(user_id, status)没联合索引,大概率扫全表
禁用触发器只是诊断手段,不是解决方案
临时禁用能确认是不是触发器拖慢,但不能长期用。不同数据库禁用方式差异大,且风险隐蔽:
- PostgreSQL:
ALTER TABLE t DISABLE TRIGGER ALL会连外键约束触发器一起关,后续INSERT可能违反约束而不报错 - SQL Server:
DISABLE TRIGGER后再ENABLE,若中间出错,触发器可能残留为 disabled 状态 - MySQL:没原生禁用命令,常用
RENAME TABLE t TO t_off隔离验证,但应用层必须同步切表,容易漏配 - 禁用后手动补逻辑?得确保幂等性——比如日志表重复写入、计数字段多加了一次,很难回滚
最常被忽略的性能黑洞:JSON 和函数包装
触发器里看似简单的字段取值,裹上函数就成性能杀手:
-
json_extract(NEW.payload, '$.user.id')→ 每次都完整解析整个 JSON 字符串 -
to_timestamp(NEW.ts_str)→ 类型转换 + 函数计算,无法复用执行计划 -
NEW.id::TEXT LIKE 'U%'→ 隐式类型转换让索引失效,且字符串匹配无法走索引 - 更隐蔽的是:PostgreSQL 中多次写
NEW.user_id,不如先DECLARE v_uid INT := NEW.user_id;,避免重复解析
触发器慢的本质,是把本该异步或预计算的逻辑,硬塞进强一致、高竞争的写入路径里。查不到索引、禁不了触发器、看不懂 performance_schema 的 TIMER_WAIT 单位(皮秒),往往比语法错误更致命。











