触发器中禁止远程调用或复杂查询,应仅做轻量字段补全;慎用AFTER触发器更新同表数据,避免递归修改;高并发表需合并触发器或改用生成列;调试时应禁用后逐个启用并加日志。

触发器里别做远程调用或复杂查询
mysql 触发器在事务内同步执行,AFTER INSERT 或 BEFORE UPDATE 里的任何耗时操作都会拖慢主 SQL。常见错误是触发器里调用 select ... FROM remote_db.users(跨库甚至跨实例),或者嵌套调用存储过程再查几张大表。
实操建议:
- 把远程/聚合逻辑移到应用层,触发器只做轻量级字段补全(如
SET NEW.updated_at = NOW()) - 必须查本地表时,确保被查字段有索引,且避免
SELECT *和子查询;用EXPLAIN检查触发器隐式执行的语句 - 如果业务允许,改用异步方式:触发器只写一条记录到
trigger_queue表,由后台任务消费
慎用 AFTER 触发器更新同表数据
在 AFTER UPDATE 里再对同一张表执行 UPDATE,会引发“Can’t update table ‘t’ in stored function/trigger because it is already used by statement which invoked this stored function/trigger” 错误——MySQL 明确禁止这种递归修改。
实操建议:
- 优先用
BEFORE触发器完成字段计算和赋值(如自动修正status、生成code) - 真要依赖新值做二次更新,拆成两个步骤:先
INSERT INTO log_table记录变更,再由定时任务批量处理 - 检查现有触发器是否隐含了对当前表的写操作,比如通过视图、触发器链或
INSERT ... SELECT间接触发
避免在高并发写入表上建多个触发器
每条 INSERT 触发 3 个 AFTER 触发器,等于单条语句实际执行 4 次写入(1 主 + 3 触发),TPS 直接腰斩。尤其当表日均写入超 10 万行时,触发器开销会从毫秒级升到百毫秒级。
实操建议:
- 用
SHOW TRIGGERS LIKE 'table_name'定期审计,合并功能重叠的触发器(例如把“记录创建时间”和“生成 UUID”写进同一个BEFORE INSERT) - 对日志类、统计类字段,考虑用生成列(
GENERATED column)替代触发器,如full_name VARCHAR(100) AS (CONCAT(first_name, ' ', last_name)) STORED - 监控
Handler_commit和Handler_rollback增长速率,突增可能意味着触发器引发死锁或超时回滚
触发器调试难?先关掉再逐个启用
触发器不报错但结果不对,往往是因为条件判断写错(如 if OLD.status != NEW.status THEN 没处理 NULL)、或时间点选错(该用 BEFORE 却用了 AFTER)。更麻烦的是,错误可能只在特定数据组合下暴露。
实操建议:
- 临时禁用:用
DROP TRIGGER IF EXISTS trigger_name,测试完再重建;不要依赖DISABLE TRIGGER(MySQL 不支持) - 加日志:在触发器里插入调试记录到独立小表,如
INSERT INTO debug_log VALUES (NOW(), 'user_update', OLD.id, NEW.status) - 用
SELECT @@autocommit确认当前会话是否自动提交,否则触发器中的INSERT可能被意外回滚
触发器本身不缓存、不并行、不异步,它的性能天花板就是单条语句的执行时间。很多所谓“优化”,本质是承认触发器不适合干的事——比如实时统计、跨服务通知、etl 预处理。该移出去的逻辑,早移早省心。