EXPLAIN看不到触发器内慢SQL,因其属过程引擎同步调用,游离于查询优化器之外;需通过交叉验证(如测试/生产库性能差异、INNODB STATUS锁等待、data_lock_waits等待链)定位,并单独EXPLAIN触发器内语句。

触发器性能问题不会报错、不进执行计划、不显在慢日志主语句里——它只会让一条 UPDATE 从 5ms 慢成 800ms,而你查 EXPLAIN 还以为一切正常。
为什么 EXPLAIN 看不到触发器里的慢 SQL
所有主流数据库(MySQL、PostgreSQL、SQL Server、Oracle)的执行计划都不展开触发器逻辑。它不是“优化器没尽力”,而是设计如此:EXPLAIN 只规划主 DML 语句的访问路径;触发器属于 PL/SQL/T-SQL 附加动作,由过程引擎同步调用,完全游离于查询优化器之外。
常见误判现象:
- 在 MySQL 中对
UPDATE t SET x=1 WHERE id=123执行EXPLAIN,输出干净利落,但实际耗时 400ms - SQL Server 的图形执行计划里只有
Clustered Index Update,没任何INSERT INTO audit_log节点,但sys.dm_exec_requests显示wait_time占总耗时 90% - Oracle 的
DBMS_XPLAN.DISPLAY_CURSOR输出里搜不到TRIGGER或PLSQL字样,哪怕触发器里跑了三张大表JOIN
怎么确认是触发器拖慢了主语句
不能只比执行时间,要交叉验证等待链和行为差异:
- 同一
UPDATE在测试库快、生产库慢,且生产库有触发器而测试库无(最直接证据) -
SHOW ENGINE INNODB STATUS显示事务状态为LOCK WAIT,但trx_query只写UPDATE t,而阻塞对象是另一张表(如t_audit),说明卡在触发器写入环节 - 查
performance_schema.data_lock_waits,发现BLOCKING_ENGINE_TRANSACTION_ID对应的事务在INNODB_TRX中已无活跃语句——它主 SQL 早执行完了,正卡在触发器逻辑里 - 应用日志显示该时段有批量操作(如一次
INSERT ... VALUES (),(),()),而每个值都会激活多个触发器(例如同时更新余额、写审计、发通知)
如何安全禁用触发器做对比验证
禁用不是为了上线绕过,而是做“开/关”对照实验。不同数据库方式不同,但核心原则一致:不动结构、不删代码、保留可逆性。
- MySQL 不支持原生禁用,推荐“变量绕过法”:在触发器开头加
IF @disable_triggers IS NOT NULL THEN LEAVE proc_label; END IF;;排查时先执行SET @disable_triggers = 1;,再跑慢语句 - PostgreSQL 用
ALTER TABLE t DISABLE TRIGGER trigger_name;,注意DISABLE TRIGGER ALL会连外键约束触发器一起关,可能引发后续插入失败 - SQL Server 用
DISABLE TRIGGER tr_name ON t;,但需确认不是INSTEAD OF类型——这类触发器会完全接管原操作,开销更高 - 禁用后必须补动作:比如触发器原本写
audit_log,就得人工补一条记录或记下缺失条数,否则数据一致性就断了
触发器里哪些 SQL 最容易被放大成性能炸弹
触发器是行级同步执行的,里面任何语句都会被放大 N 倍(N = 主语句影响行数)。一个看着无害的点查,在 10 万行更新里就是 10 万次全表扫描。
-
SELECT COUNT(*) FROM orders WHERE user_id = NEW.user_id:如果orders.user_id没索引,就是 10 万 × 全表扫描 -
UPDATE t_balance SET total = total + NEW.amount WHERE user_id = NEW.user_id:若无索引,每行都持 X 锁,极易升级为表锁 -
INSERT INTO log_table SELECT * FROM temp_cache WHERE ref_id = NEW.id:这种INSERT ... SELECT在触发器里会持间隙锁,阻塞并发插入 -
jsonb_extract_path_text(NEW.payload, 'user', 'id'):每次调用都重新解析整个 JSON 字段,字段越大越慢;应先用jsonb_typeof()判断类型,避免对null或字符串报错
真正难缠的不是语法多复杂,而是它藏得深、放大狠、验证难——你得把触发器当成独立模块,单独 EXPLAIN 它里面的每条 SQL,而不是只盯着主语句看。











