必须用before触发器配合signal sqlstate '45000'才能真正拦截非法数据,因为signal在before阶段可中断语句并触发自动回滚,而after触发器中数据已落库,仅报错无法撤回;mysql不支持触发器内rollback,且5.6以下版本需hack方式实现但不可靠。

必须用 BEFORE 触发器 + SIGNAL,其他方式都拦不住非法数据写入。
为什么 SIGNAL 是唯一能真正拦截的手段
MySQL 触发器不能控制事务,ROLLBACK 或 COMMIT 会直接报错 ERROR 1305 (42000): FUNCTION does not exist——这不是函数缺失,是语法级禁止。只有 SIGNAL SQLSTATE '45000' 能让外层 INSERT/UPDATE 立即中断,并触发自动回滚。
- AFTER 触发器里写
SIGNAL没用:数据早已落盘,错误只通知客户端,不撤回变更 - MySQL 5.6 及更早版本不支持
SIGNAL,只能靠INSERT INTO nonexistent_table这类 hack,但不可靠、难定位 -
MESSAGE_TEXT必须显式写出,漏掉会报ERROR 1337 (42000) - 字符串要用单引号:
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户状态非 active'
BEFORE 触发器里怎么写校验逻辑才不踩坑
校验必须放在 BEFORE INSERT 或 BEFORE UPDATE 中,且所有字段访问都要防 NULL。
- 别写
NEW.status = 'active',要写NEW.status IS NOT NULL AND NEW.status != 'active' - 跨表查状态(如查
users表确认用户是否active)时,users.id必须有索引,否则高并发下变全表锁 - 字符串长度校验用
CHAR_LENGTH(NEW.name) > 50,不是LENGTH()(后者按字节) - 数字范围检查要写完整:
NEW.price = 10000 - 拼接动态提示用
CONCAT():SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = CONCAT('金额超限:', NEW.amount),否则NULL会让整条SIGNAL静默失效
哪些操作在触发器里绝对要避免
看似合理,实则极易引发锁表、死锁或语法错误。
- 在触发器里
SELECT当前正被操作的表(如BEFORE INSERT ON orders里又SELECT FROM orders),直接报ERROR 1442 (HY000) - 用
COUNT(*) > 0判断存在性——改用EXISTS (SELECT 1 FROM ...),否则大表上性能断崖下跌 - 调用含
COMMIT/ROLLBACK的存储过程,哪怕它声明为READS SQL DATA,只要内部有事务语句就崩 - 在触发器里更新其他库的表:
UPDATE other_db.table_name会报ERROR 1142,跨库只读允许,写入必须由应用层承担 - 对
NEW.col赋NULL却没设STRICT_TRANS_TABLESsql_mode,MySQL 会静默转成默认值,你以为拦住了,其实没拦住
最易被忽略的是:触发器本身可能被绕过——LOAD DATA INFILE 或设置 sql_log_bin = OFF 时,触发器默认不执行。上线前必须用真实 INSERT/UPDATE 语句验证,而不是只测 SELECT。











