触发器中避免嵌套多条dml、跨库查询和无索引select,优先用on duplicate key update合并操作;存储过程须显式事务控制与错误处理;权限配置需指定低权限definer并确保必要授权。

触发器里写 UPDATE/INSERT 太多,SQL 执行变慢
MySQL 触发器在 INSERT、UPDATE、DELETE 时自动执行,但每条语句都触发一次,如果里面再嵌套多条 DML(比如反复 UPDATE 其他表),会显著拖慢主 SQL 的响应。尤其是高频写入场景(如订单流水、日志记录),一个 BEFORE INSERT 里做 3 次 UPDATE,实际耗时可能翻倍。
实操建议:
- 把触发器里「非强一致性依赖」的操作剥离出去,比如统计类字段更新、异步通知、日志归档——这些改用应用层定时任务或消息队列处理
- 必须保留在数据库侧的逻辑,优先合并为单条语句:用
INSERT ... ON DUPLICATE KEY UPDATE替代先SELECT再INSERT/UPDATE - 避免在触发器中调用自定义函数(
GET_CURRENT_USER_RANK()这类),函数执行开销不可控,且无法走索引 - 检查是否误用了
AFTER触发器做本可用BEFORE完成的事——AFTER会多一次事务提交等待
触发器引用了未加索引的字段导致锁表
常见现象是:某张表加了 BEFORE UPDATE 触发器,里面有一句 SELECT COUNT(*) FROM log_table WHERE status = NEW.status AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY),但 log_table(status, created_at) 没复合索引。结果每次更新都全表扫描+行锁,其他写入被卡住。
实操建议:
- 触发器内所有
SELECT必须走索引,用EXPLAIN显式验证,尤其注意NEW和OLD引用的字段是否出现在索引最左前缀 - 禁止在触发器里做跨库查询(
SELECT * FROM other_db.users),跨库意味着额外连接与网络延迟,且无法利用当前事务上下文 - 时间范围条件(如
created_at > '2024-01-01')务必配合日期字段的前缀索引或分区表,否则容易退化为全表扫描
用存储过程替代触发器后,事务边界没对齐
把原来分散在多个触发器里的逻辑收进一个存储过程(proc_update_order_status),看似更可控,但如果没显式管理事务,反而更容易出问题:比如存储过程中 INSERT INTO audit_log 成功了,但后续 UPDATE order_main 失败,audit_log 就留下脏数据。
实操建议:
- 存储过程开头必须加
DECLARE EXIT HANDLER FOR SQLEXCEPTION,并在 handler 里做ROLLBACK,不能依赖调用方事务 - 如果原逻辑分布在
BEFORE和AFTER两个触发器,迁移到存储过程后,要手动模拟执行顺序:比如先校验(对应 BEFORE),再主更新,最后补日志(对应 AFTER) - 存储过程参数命名需和触发器变量一致(如用
p_order_id而非id),避免应用层传参错位,调试时难定位 - 别为了“统一”把读操作(
SELECT)也塞进存储过程——纯查询没必要进事务,拆出来单独调用更轻量
触发器 + 存储过程混合使用时权限配置遗漏
开发本地测试没问题,上线后报错:ERROR 1419 (HY000): You do not have the SUPER privilege and binary logging is enabled。这是因为 MySQL 开启了 binlog(生产环境基本都开),而含 CREATE PROCEDURE 或修改数据的触发器,需要 DEFINER 用户有 SUPER 权限——但线上账号通常被严格限制。
实操建议:
- 创建触发器或存储过程时,显式指定低权限
DEFINER,例如DEFINER = 'app_user'@'%',并确保该用户已授予EXECUTE和必要表的SELECT/INSERT/UPDATE - 禁用
log_bin_trust_function_creators=1是临时解法,不推荐;应通过权限收敛而非放宽安全策略 - 用
SHOW CREATE TRIGGER trigger_name检查当前 DEFINER,避免因账号迁移导致定义失效
触发器不是不能用,而是它的执行时机、事务绑定、错误传播路径都比普通 SQL 更隐蔽。哪怕只是把一条 UPDATE 从触发器挪到存储过程,也要同步确认调用方是否已包裹在事务里、失败时能否回滚、binlog 是否记录完整。这些细节不盯住,优化就只在表面打转。











