触发器中insert into ... select易卡住,因锁持有与查询低效;new/old依触发时机受限;原生时间戳优于触发器;非确定函数致复制异常;触发器共享事务上下文。

触发器里写 INSERT INTO ... SELECT 为什么总卡住?
说明:这是最典型的性能陷阱。触发器执行时会持有当前事务的行锁或表锁,如果 SELECT 涉及大表、没走索引,或结果集太大,就会拖慢主 DML 操作,甚至引发死锁。
实操建议:
- 避免在
BEFORE或AFTER触发器中直接做跨表聚合查询 - 如果必须查其他表,确保
SELECT的WHERE条件能命中索引,且加LIMIT 1(如只取最新一条) - 更稳妥的做法是把这类逻辑抽到应用层,或改用异步队列(如 MySQL + Kafka / RabbitMQ)
常见错误现象:Lock wait timeout exceeded、Deadlock found when trying to get lock
NEW 和 OLD 在不同触发时机下的可用性
说明:NEW 和 OLD 不是任何时候都能用。比如 BEFORE INSERT 中没有 OLD,AFTER DELETE 中没有 NEW,强行引用会报错 Unknown column 'OLD.xxx' in 'field list'。
实操建议:
-
INSERT触发器:可用NEW,不可用OLD -
UPDATE触发器:NEW和OLD都可用,但注意字段是否被 SET 修改过(未修改的字段值仍为原值) -
DELETE触发器:可用OLD,不可用NEW - 所有触发器中,不能对触发它的表做 DML(如
INSERT触发器里再INSERT同一张表),MySQL 会直接报错Can't update table 'xxx' in stored function/trigger
用触发器实现“自动更新时间戳”反而更慢?
说明:很多人以为 ON UPDATE CURRENT_TIMESTAMP 不够灵活,就自己写触发器来更新 updated_at。但触发器调用本身有开销,尤其在批量 UPDATE 时,每行都执行一次触发器逻辑,比原生时间戳机制慢得多。
实操建议:
- 优先用原生列定义:
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - 若需兼容旧数据或动态计算(如依赖其他字段),才考虑触发器,并确保只更新必要字段,避免全字段
SET - 测试时对比单条和批量(如
UPDATE ... LIMIT 1000)的耗时差异,容易忽略的是:触发器不会被批量优化,而原生机制会
触发器里调用存储函数导致复制失败?
说明:MySQL 主从复制默认是语句级(SBR),如果触发器里调用了非确定性函数(如 NOW()、RAND()、自定义函数未声明 DETERMINISTIC),从库重放时可能产生不一致,甚至中断复制。
实操建议:
- 自定义函数必须显式声明:
DETERMINISTIC(确定性)、NO SQL或READS SQL DATA - 避免在触发器中调用含副作用的函数(如写日志表、发 HTTP 请求)
- 如果必须用不确定逻辑,把 binlog 格式切到
ROW(binlog_format = ROW),但要注意磁盘和网络开销变大
触发器不是黑盒魔法,它和主 SQL 共享事务上下文,也共享锁和执行计划——这点最容易被忽略。










