必须用 before insert 是因为数据校验需在写入前拦截,after insert 时数据已落盘,signal 仅报错无法回滚该行;before 中可读写 new 字段做修正或主动报错,确保非法值被真正拦住而非事后追责。

能限制,但必须用 BEFORE INSERT 触发器配合 SIGNAL 主动报错,不能靠静默修正或忽略 —— 否则非法值照进不误。
为什么必须用 BEFORE INSERT 而不是 AFTER
AFTER INSERT 触发时数据已写入磁盘,SIGNAL 只能报错,无法回滚该行(除非整个事务被上层中断)。业务上要的是“拦住”,不是“事后骂一句”。很多线上事故就源于误用了 AFTER,日志里看到报错,但表里早多了几条脏数据。
-
BEFORE INSERT中可读NEW.column_name,且支持SET NEW.column = ...做轻量修正(如空字符串转NULL) -
AFTER INSERT里NEW已不可修改,查本表还会触发Can't update table 'xxx' in stored function/trigger - Navicat 等工具默认可能选
AFTER,建触发器时务必手动切到Before
金额字段精度校验不能只比数值
对 DECIMAL(10,2) 字段,123.456 是合法数字,但 MySQL 会静默四舍五入成 123.46。你要拦的不是“非数字”,而是“原始输入超精度”。
- 必须用
CAST(NEW.amount AS CHAR)获取字符串形态,再用LOCATE('.', value)找小数点位置 - 别用
INSTR()—— 它无小数点时返回 0,和位置 0 混淆;LOCATE更明确 - 记得先
TRIM(),否则"123.45 "过了 CAST 变成"123.45",躲过检查 - 示例片段:
SET @str = TRIM(CAST(NEW.amount AS CHAR));<br>SET @dot_pos = LOCATE('.', @str);<br>IF @dot_pos > 0 AND LENGTH(@str) - @dot_pos > 2 THEN<br> SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Amount exceeds 2 decimal places';<br>END IF;
多个字段或批量插入时的性能陷阱
触发器是逐行执行的。单条 INSERT 感觉不到压力,但 INSERT INTO t VALUES (),(),()... 或 LOAD DATA INFILE 导入 10 万行,就会触发 10 万次校验逻辑 —— 容易拖慢导入速度十倍以上。
- 每个金额字段(如
price、tax、discount)都要单独写IF判断,SIGNAL信息里带上字段名,方便定位:CONCAT('Field ', '''price''', ' exceeds precision') - 避免在触发器里做跨表查询(如查用户状态),否则每行都触发一次关联查询,IO 和锁开销陡增
- 如果业务允许,优先把精度校验前移到应用层或 JSON 解析阶段;触发器留作兜底,而非主力
真正难的不是写对一行 SIGNAL,而是想清楚:这个校验到底该由数据库承担,还是该交给更可控的应用层?一旦加了触发器,所有绕过 ORM 直连 MySQL 的脚本、ETL 工具、DBA 临时修复操作,都会撞上它 —— 这个副作用,上线前得所有人心里有数。










