signal 是唯一可靠的选择,因为mysql触发器无法控制事务,显式rollback或commit会报错,而signal可在before阶段抛出异常使外层语句中断并自动回滚。

必须用 SIGNAL,不能写 ROLLBACK 或调用含事务控制的存储过程——否则直接报错,且非法数据可能已写入。
为什么 SIGNAL 是唯一可靠的选择
MySQL 触发器天然运行在父语句的事务中,自己无权开启或结束事务。任何显式 COMMIT、ROLLBACK 或调用含这些语句的存储过程,都会触发 ERROR 1305 (42000): FUNCTION does not exist(不是函数缺失,是语法禁止)。而 SIGNAL 会主动抛出异常,让外层 INSERT/UPDATE 立即中断,并触发自动回滚——这是 MySQL 唯一支持的“强制拒绝”方式。
- MySQL 5.6 及更早版本不支持
SIGNAL,只能靠INSERT INTO nonexistent_table等 hack 方式,但不可靠、难定位、不推荐 -
SIGNAL SQLSTATE '45000'是通用错误码,可自定义MESSAGE_TEXT,应用层能直接捕获并展示 - 别漏写分号:
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户状态非 active';后必须有;
BEFORE 触发器里 SIGNAL 才真正有效
AFTER 触发器里写 SIGNAL 是典型误区:此时数据早已落盘,报错只会让事务回滚,但主表记录可能已写入(尤其在非事务引擎如 MyISAM 下完全无法回滚)。只有 BEFORE INSERT / BEFORE UPDATE 阶段,才能在数据写入前拦截。
- 校验逻辑必须包裹在
IF ... THEN ... END IF;中,避免 NULL 导致条件失效 - 例如检查
NEW.status是否为'active',要写IF NEW.status IS NOT NULL AND NEW.status != 'active' THEN ... - 跨表查询(如
SELECT status FROM users WHERE id = NEW.user_id)必须确保users.id有索引,否则高并发下变全表锁
常见 SIGNAL 写法与易错点
错误提示太笼统?默认 MESSAGE_TEXT 不带上下文,调试时根本看不出哪条字段、哪个值触发了校验。建议把关键信息拼进去:
- 用
CONCAT()拼接动态内容:SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = CONCAT('订单金额超限:', NEW.amount, ' > ', OLD.max_amount); - 字符串长度校验别用
LENGTH()(按字节),改用CHAR_LENGTH(NEW.name) > 50 - 对数字范围校验,别只写
NEW.price ,要写完整:<code>NEW.price 10000 - 所有涉及
NEW或OLD的字段访问,都应先判断是否为 NULL,否则比较结果恒为 FALSE
最常被忽略的是:触发器本身是否启用、是否被绕过(比如用 LOAD DATA INFILE 或禁用 sql_log_bin 时,触发器默认不执行)。上线前务必用真实 DML 语句验证,而不是只测 SELECT。











