触发器会显著拖慢高并发写入场景
MySQL 触发器在
INSERT、
UPDATE、
DELETE执行期间同步运行,属于事务的一部分。这意味着:只要触发器逻辑没跑完,该行(甚至整个表,取决于隔离级别和锁策略)就无法被其他事务安全修改。在每秒数百次以上写入的业务中(如订单创建、日志上报),一个耗时 5ms 的触发器就能让
innodb_row_lock_time_avg明显升高,进而拉低整体
TPS。
触发器里的 SELECT 或远程调用是并发杀手
以下操作在触发器中尤其危险:
在BEFORE INSERT里执行
SELECT ... FROM other_table WHERE x = NEW.y—— 可能引发间隙锁或锁升级,阻塞其他插入 调用
SLEEP()、
UUID()(部分版本)、或自定义函数(含外部 HTTP 请求)—— 直接延长事务持有时间 对同一张表做
INSERT/UPDATE(比如自增统计表)—— 极易触发
Deadlock found when trying to get lock
DELIMITER $$ CREATE TRIGGER order_after_insert_log AFTER INSERT ON orders FOR EACH ROW BEGIN -- ❌ 危险:跨表 UPDATE + 可能锁住 users 表的某一行 UPDATE users SET last_order_time = NOW() WHERE id = NEW.user_id; END$$ DELIMITER ;
替代方案比优化触发器更有效
多数“必须用触发器”的需求,其实有更低开销的选择:
把计数、状态更新等逻辑移到应用层,在主事务提交后异步处理(例如发 MQ 消息) 用INSERT ... ON DUPLICATE KEY UPDATE替代
BEFORE INSERT中的查重逻辑 对审计类日志,改用
BINLOG解析(如 Canal / Debezium),完全绕过事务链路 真需要强一致性且逻辑简单?优先考虑生成列(
GENERATED COLUMN)或检查约束(
CHECK CONSTRAINT)
监控触发器真实开销不能只看 slow log
slow_query_log默认不记录触发器内部语句,容易误判。要定位瓶颈,必须开启:
performance_schema=ON+ 启用
events_statements_history_long查
performance_schema.events_statements_history_long中
EVENT_NAME包含
trigger的记录 重点关注
LOCK_TIME和
EXECUTION_ENGINE(InnoDB vs MyISAM 行为差异大)
一个常被忽略的事实:即使触发器体为空,每次触发仍带来约 0.1–0.3ms 的固定上下文切换开销。在 QPS 超 2000 的写入集群里,这已足够让 p99 延迟跳变。
