不能在触发器里写复杂业务逻辑——这是必须遵守的边界,因其会将触发器从“一致性守门人”变为“事务阻塞炸弹”;应将积分计算、http调用、多表嵌套更新等移至应用层或存储过程,触发器仅作轻量校验与数据修正。

不能在触发器里写复杂业务逻辑——这不是限制,而是必须遵守的边界。一旦把积分计算、HTTP调用、多表嵌套更新塞进触发器,它就从“保障一致性的守门人”变成“阻塞事务的定时炸弹”。
BEFORE 触发器中修改 NEW 值的正确姿势
BEFORE INSERT 或 BEFORE UPDATE 是唯一能安全改写即将写入数据的地方。比如自动补全时间戳、标准化手机号格式、或根据规则生成编号。
-
NEW.created_at可在BEFORE INSERT中赋值为CURRENT_TIMESTAMP,但不能在AFTER INSERT中操作(会静默失败) - 对
NEW.phone做REPLACE(REPLACE(NEW.phone, '-', ''), ' ', '')是合理清洗;但调用自定义函数做号码归属地查询就超纲了 - MySQL 5.7+ 中,
NEW字段可写仅限于BEFORE时机;AFTER下所有NEW/OLD都只读 - SQLite 不支持
NEW.column = ...赋值语法,得用UPDATE ... SET column = ... WHERE rowid = NEW.rowid曲线救国(但慎用,易引发递归)
INSTEAD OF 触发器必须配合视图使用
想让 UPDATE v_user_summary 看似更新一个聚合视图,实则拆解为对底层 users 和 profiles 表的操作?只能靠 INSTEAD OF + 视图组合实现。
- 直接对普通表定义
INSTEAD OF触发器是语法错误——SQLite/MySQL 都不支持 - 视图本身不可更新时(含
GROUP BY、DISTINCT、子查询),INSTEAD OF是唯一出路 - 触发器内必须显式处理
INSERT/UPDATE/DELETE分支,不能只写一个UPDATE就覆盖全部操作 - 别在
INSTEAD OF UPDATE里漏掉WHERE id = OLD.id条件,否则可能误更新整张表
跨表操作必须带 WHERE 条件且依赖索引
触发器里执行 UPDATE orders SET status = 'archived' WHERE user_id = NEW.id 看似简单,但若 orders.user_id 没索引,每次插入用户都会触发全表扫描。
- 所有
SELECT/UPDATE/DELETE必须基于索引字段过滤,禁止无条件操作 - 避免在触发器里做
JOIN查询:MySQL 会锁住关联表,SQLite 可能因事务嵌套失败 - 级联更新优先用外键
ON UPDATE CASCADE实现,比触发器更轻量、更可靠 - 审计日志类操作尽量用
AFTER+ 异步队列替代,而不是在触发器里INSERT INTO audit_log同步写
报错必须用 SIGNAL,不能用 ROLLBACK
校验失败时想中断当前 INSERT?ROLLBACK 在触发器里会直接报错 ERROR 1305 (42000),因为触发器没有独立事务上下文。
- MySQL 5.7+ 正确写法:
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足'; - MySQL 5.6 及更早版本只能用
INSERT INTO nonexistent_table VALUES()制造异常,但不推荐 - SQLite 没
SIGNAL,得靠RAISE(ABORT, 'message'),且仅在BEFORE触发器中生效 - 别在触发器里调用含
COMMIT的存储过程——哪怕它声明为READS SQL DATA,也大概率出错
最常被忽略的一点:触发器逻辑是否会被批量操作放大 N 倍?导入 10 万行数据时,哪怕只是 INSERT INTO log 一句,也会执行 10 万次。真正的复杂业务,该放在应用层队列里做,而不是塞进数据库的齿轮缝里。











