触发器中禁止查询大表,应仅用new/old引用本行字段;必须关联数据时优先冗余字段;避免调用存储过程或函数;清理调试语句;批量操作前禁用触发器。

触发器里别查表,尤其是大表
MySQL 触发器在 INSERT/UPDATE/DELETE 语句执行期间同步运行,没有异步机制。如果触发器里写了 SELECT 去查另一个大表(比如查用户信息、查配置表),就会卡住主事务,拖慢整个操作。更糟的是,这种查询还可能引发锁等待——特别是查的表正在被其他事务修改时。
- 尽量只用
NEW和OLD引用本行字段,避免额外SELECT - 真要关联数据,优先考虑把必要字段冗余到当前表(比如在订单表里存
user_id和user_name,而不是每次去users表查) - 如果必须查外部表,确认该表有合适索引,且查询条件能命中索引最左前缀
避免在触发器里调用存储过程或自定义函数
看起来封装好了,但实际会放大开销。每个函数调用都是一次上下文切换,如果函数内部还有循环、临时表或复杂逻辑,性能损耗会指数级上升。尤其当触发器作用于批量操作(如 INSERT INTO ... SELECT 或 LOAD DATA)时,函数会被反复执行 N 次。
- 把简单计算逻辑直接写进触发器体,比如
NEW.updated_at = NOW() - 复杂业务逻辑一律移到应用层或定时任务里处理
- 如果非要用函数,确保它是
DETERMINISTIC且不含 SQL,否则 MySQL 可能拒绝创建触发器
检查触发器是否被重复创建或误加了调试语句
线上环境偶尔会因部署脚本问题,导致同一个触发器被重复创建多次(虽然 MySQL 不允许同名触发器,但不同库/不同事件类型容易混淆)。更常见的是开发时加的 SELECT 'debug' 或 INSERT INTO debug_log 没删干净——这些语句在高并发下会迅速成为瓶颈。
- 查看触发器定义:执行
SHOW CREATE TRIGGER trigger_name - 确认触发器数量合理:
SELECT * FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_TABLE = 'your_table'; - 删除无用触发器:
DROP TRIGGER IF EXISTS trigger_name; - 所有日志类语句必须清理,触发器不是调试工具
批量操作时触发器开销会被放大
单条 INSERT 慢一点可能不明显,但 INSERT INTO t1 SELECT * FROM t2 LIMIT 10000 这种操作会让触发器执行一万次。此时哪怕每次只花 0.5ms,总耗时也到 5 秒,还会阻塞表锁或行锁。
- 对大批量导入/同步场景,先
DISABLE TRIGGER(MySQL 8.0.19+ 支持ALTER TABLE ... DISABLE TRIGGER),处理完再启用 - 更稳妥的做法是绕过触发器:用应用层或存储过程完成等效逻辑,再一次性写入结果
- 如果必须保留触发器行为,考虑改用物化视图(MySQL 不原生支持,可用汇总表 + 定时任务模拟)或 CDC 工具(如 Debezium)做后置处理
触发器本质是“隐式耦合”,它让数据库行为变得不可见、难测试、难压测。真正难优化的往往不是语法本身,而是没人意识到:那条看似无害的 SELECT 正卡在每一条 UPDATE 的关键路径上。











