触发器内子查询每次update都同步执行且易全表扫描,必须单独explain分析;应优先移除而非优化,改用预计算字段、应用层传参或单点主键查询。

触发器里的子查询不是“可能慢”,而是每次UPDATE都会同步执行一遍,且极大概率走全表扫描——只要EXPLAIN显示type=ALL或select_type=DEPENDENT SUBQUERY,就该立刻重写。
为什么EXPLAIN不显示触发器,但必须单独分析它里面的SQL
MySQL的EXPLAIN只分析主语句,完全不展开触发器逻辑。你看到UPDATE t SET x=1 WHERE id=123的type=eq_ref,不代表快;真正卡住的是触发器里那句SELECT SUM(amount) FROM orders WHERE user_id = NEW.user_id。这句没被EXPLAIN覆盖,却在事务内真实执行、锁表、扫磁盘。
- 把触发器中每一条独立
SELECT、UPDATE或INSERT语句单独拎出来,加EXPLAIN前缀运行 - 重点盯
key是否为NULL、rows是否超1万、Extra是否含Using temporary或Using filesort - 若子查询里有
WHERE status = UPPER(NEW.status),UPPER()会让status索引彻底失效,type必为ALL
子查询在触发器里变慢的典型EXPLAIN信号
直接看触发器内子查询的EXPLAIN输出,比看主语句更有诊断价值。以下三种输出基本等于性能红灯:
-
select_type = DEPENDENT SUBQUERY:表示外层每行都触发一次内层查询,1000行更新 = 执行1000次子查询 -
type = ALL且rows > 10000:哪怕子查询只查一张表,没索引就等于全表扫,IO拉满 -
key = NULL但字段明明建了索引:常见于类型不一致(如orders.user_id是BIGINT,而子查询里用CAST(? AS SIGNED))、或条件用了函数(DATE(created_at))
怎么改?优先砍掉子查询,而不是优化它
触发器里根本不是“优化子查询”的场景,而是“能不能不用子查询”。同步执行+行级触发+子查询 = 高并发下的定时炸弹。
- 把
SELECT COUNT(*) FROM log WHERE ref_id = NEW.id这种,改成应用层先查好,作为参数传入UPDATE语句 - 必须保留聚合?改用预计算字段:
ALTER TABLE users ADD COLUMN order_count INT DEFAULT 0,在订单表AFTER INSERT里只做UPDATE users SET order_count = order_count + 1 WHERE id = NEW.user_id - 实在要查关联表?只允许单点主键查询:
SELECT name FROM users WHERE id = NEW.user_id LIMIT 1,禁用IN (SELECT ...)和NOT IN(NULL会导致全表扫描)
最容易被忽略的是:触发器里每一次NEW.xxx取值,如果裹在函数里(比如json_extract(NEW.payload, '$.user.id')),都是一次完整JSON解析,没有缓存、无法复用。先用局部变量存住:SET @uid := json_extract(NEW.payload, '$.user.id');,再在后续所有地方用@uid——这点在批量更新时放大效应极明显。










