必须用signal,不能写rollback或commit;触发器运行在父语句事务上下文中,无权控制事务,仅signal可在before触发器中中断dml并触发自动回滚。

必须用 SIGNAL,不能写 ROLLBACK 或 COMMIT
MySQL 触发器运行在父语句的事务上下文中,自己无权开启或结束事务。任何显式 ROLLBACK、COMMIT,或调用含这些语句的存储过程,都会直接报错:ERROR 1305 (42000): FUNCTION does not exist——这不是函数缺失,是语法禁止。只有 SIGNAL 能让外层 INSERT/UPDATE/DELETE 立即中断,并触发自动回滚。
SIGNAL 必须放在 BEFORE 触发器里
在 AFTER 触发器中执行 SIGNAL 会报错,但原 DML 已经生效,无法回滚,业务约束完全失效。所以校验逻辑只能放 BEFORE INSERT、BEFORE UPDATE 或 BEFORE DELETE 中。
- 错误写法:
CREATE TRIGGER ... AFTER INSERT ... SIGNAL SQLSTATE '45000' ...→ 数据已插入,再抛错也没用 - 正确写法:
CREATE TRIGGER check_name_before_insert BEFORE INSERT ON users ... IF NEW.name IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Name cannot be null'; END IF; - 注意:必须包裹在
BEGIN ... END块内,且需提前设置DELIMITER
避免常见陷阱:SQLSTATE 格式、严格模式、HANDLER 类型
SIGNAL 的 SQLSTATE 必须是 5 位字符串,标准自定义码用 '45000';若写成 '4500' 或 'HY000'(非标准状态码),部分客户端可能无法识别。同时,sql_mode 必须包含 STRICT_TRANS_TABLES,否则字段截断等隐式错误会被静默忽略,导致 SIGNAL 失效。
- 错误示例:
SIGNAL SQLSTATE '4500' SET MESSAGE_TEXT = '...';→ 语法错误 - 触发器内声明
HANDLER时,类型必须是CONTINUE,不能用EXIT,否则 handler 执行完后主语句仍可能继续,造成数据不一致 - 不要试图在触发器里捕获主键冲突(如
ERROR 1062)——触发器内部无法DECLARE HANDLER捕获这类约束错误
为什么不用存储过程替代触发器做校验?
因为触发器能天然绑定到 DML 语句上,无需应用层显式调用;而存储过程虽支持 DECLARE HANDLER 和更灵活的异常处理,但它不能被触发器调用(MySQL 明确禁止),也无法自动拦截任意来源的 DML。如果业务逻辑复杂、需重试或分支更新,应把校验+操作封装进存储过程,由应用统一调用;但如果目标是“所有写入都强制校验”,触发器仍是唯一能无感覆盖的机制——前提是只做轻量预检+SIGNAL。
真正容易被忽略的是并发场景下的预检失效:用 SELECT INTO 判断后再 IF 分支,仍可能因间隙竞争导致唯一约束冲突。这种情况下,SIGNAL 不是兜底手段,而是最后一道防线;更稳妥的做法是把约束逻辑前移到应用层,或改用 INSERT IGNORE/ON DUPLICATE KEY UPDATE 配合业务语义处理。











