触发器里别做远程调用或复杂查询
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 预处理。该移出去的逻辑,早移早省心。
