必须用 BEFORE INSERT(以及 BEFORE UPDATE),因为 AFTER 触发器在数据已写入后执行,无法阻止非法数据,且 SIGNAL 报错时主表数据已插入成功;BEFORE 阶段可修改 NEW 值或用 SIGNAL 中断操作。

触发器里用 AFTER INSERT 还是 BEFORE INSERT?
必须用 BEFORE INSERT(以及 BEFORE UPDATE)。AFTER 触发器无法阻止非法数据写入——它在记录已落盘后才执行,此时报错也改不了既成事实,还可能引发事务回滚不干净的问题。
常见错误现象:AFTER 里写 SIGNAL SQLSTATE '45000',看似报错了,但主表数据已经插入成功,校验形同虚设。
-
BEFORE阶段能直接修改新行值(比如把超限的age强制设为 0),也能用SIGNAL中断操作 - 如果业务要求“拒绝而非修正”,就只用
SIGNAL;若接受兜底修正,则先赋值再放行 - 注意:MySQL 5.7+ 才支持
SIGNAL,低版本只能靠构造除零等异常方式中断,不推荐
NEW 和 OLD 在触发器里怎么安全取值?
NEW 对应即将插入/更新的行,OLD 只在 UPDATE 或 DELETE 中可用。关键点在于:不能假设字段一定存在或非 NULL,尤其当表结构后续变更时。
使用场景:校验 price 是否在 0–10000 范围内,同时允许 price 为 NULL(表示未定价)。
- 务必用
IF NEW.price IS NOT NULL THEN ... END IF;包裹校验逻辑,避免 NULL 参与比较导致条件失效 - 别直接写
NEW.price < 0,而要写NEW.price < 0 OR NEW.price > 10000,否则范围外的正数会被漏掉 - 对字符串长度校验,用
CHAR_LENGTH(NEW.name) > 50,不是LENGTH()(后者按字节,中文易误判)
触发器报错信息不明确,怎么快速定位是哪条语句、哪个字段出问题?
MySQL 默认的 SIGNAL 错误提示太笼统,比如只显示 ERROR 1644 (45000): Unhandled user-defined exception condition,根本看不出是 age 超了还是 score 为空。
实操建议:在 SIGNAL 中拼接具体字段名和值,提升可读性:
IF NEW.age < 0 OR NEW.age > 150 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = CONCAT('Invalid age value: ', COALESCE(NEW.age, 'NULL'), ' in table users');
END IF;
-
COALESCE防止NEW.age为 NULL 时整个消息变 NULL - 错误信息里带上表名和字段名,运维查日志时不用再翻触发器定义
- 避免在消息里拼接用户输入内容(如
NEW.username),有 SQL 注入风险(虽然触发器上下文较封闭,但习惯要守)
触发器性能影响有多大?哪些操作会明显拖慢写入?
单个简单范围校验(比如数值比较)几乎无感知,但一旦涉及子查询、函数调用或跨表检查,延迟会立刻显现。最危险的是在触发器里查同一张表——直接触发 Can't update table 'xxx' in stored function/trigger 报错。
- 禁止在触发器中执行
SELECT ... FROM NEW.table_name或任何对当前表的 DML/SELECT - 避免调用自定义函数,尤其是含 I/O 或复杂逻辑的;内置函数如
COALESCE、CHAR_LENGTH是安全的 - 高并发写入场景下,触发器逻辑越短越好;如果校验规则复杂(比如需查配置表获取动态阈值),建议移到应用层,用数据库约束兜底简单规则
真正容易被忽略的是:触发器不会自动适配 INSERT ... ON DUPLICATE KEY UPDATE 的 UPDATE 分支——它只触发 BEFORE INSERT,而跳过 BEFORE UPDATE。这种语法下的字段校验必须显式覆盖两种路径。


















