触发器里不能写事务控制语句
MySQL 触发器运行在主 SQL 语句的事务上下文中,
BEGIN...END块内不允许出现
START TRANSACTION、
COMMIT或
ROLLBACK。一旦写了,会直接报错
ERROR 1305 (42000): SAVEPOINT does not exist或更常见的
ERROR 1305 (42000): FUNCTION does not exist(因解析失败误报函数不存在)。
实际业务中若需“失败回滚整个操作”,必须依赖外层事务;若需“局部补偿”,得改用存储过程 + 应用层协调,或把逻辑拆到应用代码里。
触发器中调用的存储过程也不能含COMMIT
SELECT ... FOR UPDATE在触发器里可用,但要小心死锁——尤其多表更新时 想记录日志又怕影响性能?别在触发器里写
INSERT INTO audit_log,改用异步消息或延迟写入
NEW 和 OLD 的只读性与 NULL 边界问题
NEW在
BEFORE INSERT和
BEFORE UPDATE中可赋值,用于修改即将插入/更新的行;
OLD在
UPDATE和
DELETE中只读。但很多人忽略:对
NEW.col赋值时,如果该列是
NOT NULL且你赋了
NULL,MySQL 不会报错,而是静默转为空字符串或默认值——取决于 SQL mode。
例如:
SET NEW.status = NULL;对
status VARCHAR(20) NOT NULL DEFAULT 'active'列,结果是
'active',不是预期的空值。 检查
sql_mode,确认是否含
STRICT_TRANS_TABLES,否则赋值失效不易察觉
NEW.col赋值后,后续触发器或主语句仍以该值为准,但不会触发二次校验(如 CHECK 约束) 在
AFTER触发器中访问
NEW没问题,但不能修改——会报
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
避免在触发器中调用耗时函数或远程服务
触发器执行是同步阻塞的,哪怕只是调用一个
SLEEP(0.1),也会让整条
INSERT延迟 100ms。更常见的是误用
UUID()、
RAND()、
SYSDATE(),或在触发器里拼接 HTTP 请求(通过
sys_exec或 UDF),导致连接池打满、主从延迟飙升。
真实案例:某订单表
BEFORE INSERT触发器调用
GET_LOCK('order_seq', 3) 生成单号,高并发下锁争用严重,TPS 从 1200 掉到 80。
时间类函数优先用 NOW()(事务开始时间),而非
SYSDATE()(执行时刻),避免主从不一致 需要唯一编号?用自增 ID + 业务前缀,或提前在应用层生成,别在触发器里查表+加锁 绝对不要在触发器里调用
SELECT ... FROM remote_db.table—— 即使是同实例,跨库查询也可能引发元数据锁等待
触发器调试难,上线前必须做三件事
MySQL 没有触发器断点调试能力,错误信息也常模糊(比如
ERROR 1442 (HY000): Can't update table 't' in stored function/trigger because it is already used by statement which invoked this stored function/trigger)。靠日志和复现成本极高。
上线前务必验证:
在测试库开general_log = ON,手动执行触发语句,确认日志里没有意外的嵌套
INSERT/UPDATE用
SHOW TRIGGERS LIKE 'table_name';核对触发时机(
BEFORE/AFTER)、事件(
INSERT/UPDATE/DELETE)和定义体是否和预期一致 压测时监控
Threads_running和
Innodb_row_lock_waits,若二者同步上涨,大概率是触发器引发锁竞争
复杂逻辑真没必要硬塞进触发器——它适合轻量、确定、无副作用的操作。业务越重,越容易变成线上故障的隐藏开关。
