触发器中禁止查询触发表,只能用OLD/NEW引用比对字段;多事件需分写触发器;抛错必须用SIGNAL或RAISE EXCEPTION;避免大字段、JSON全量比对及外部调用;复杂校验应移至应用层或异步处理。

触发器里不能用 SELECT ... INTO 变量来查比对结果
很多开发者想在 BEFORE UPDATE 触发器里查旧值和新值差异,顺手写 SELECT old_col INTO @diff,但 MySQL 严格禁止在触发器中对触发该触发器的表做 SELECT(会报 ERROR 1442: Can't update table 't' in stored function/trigger because it is already used by statement which invoked this stored function/trigger)。这不是语法限制,而是防止递归或死锁的底层保护。
- 真正能安全读取旧值/新值的,只有
OLD.col和NEW.col这两个内置引用 - 如果需要比对逻辑复杂(比如要查关联表状态),得把校验逻辑拆到应用层,或改用存储过程+事件轮询(不实时但可控)
- PostgreSQL 的触发器允许执行任意查询,但同样不建议在
BEFORE阶段查本表——容易拖慢主事务,且无法保证隔离级别下看到一致快照
INSERT/UPDATE/DELETE 触发器必须分开写,不能合并监听
MySQL 不支持一个触发器响应多个事件类型(比如 CREATE TRIGGER t AFTER INSERT, UPDATE ON tbl 是非法语法),PostgreSQL 虽然支持 FOR EACH ROW EXECUTE FUNCTION f() 并在函数内用 TG_OP 判断操作类型,但实际比对逻辑往往因操作类型不同而差异很大。
-
INSERT:只需检查新值是否符合业务规则(如金额非负),无旧值可比 -
UPDATE:必须逐字段对比OLD.col != NEW.col,注意NULL比较要用IS DISTINCT FROM(PostgreSQL)或(OLD.col IS NULL) != (NEW.col IS NULL) OR OLD.col != NEW.col(MySQL) -
DELETE:常被忽略,但有些一致性要求(如“子表必须为空才能删父记录”)就得在这里拦截,否则数据就断了
触发器抛错用 SIGNAL 而不是 RETURN 或 PRINT
想让不一致的数据进不来,必须中断当前语句。MySQL 用 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'data inconsistent';PostgreSQL 用 RAISE EXCEPTION 'data inconsistent'。写成 RETURN 或 PRINT 不仅无效,还会让触发器静默失败,数据照进不误。
- MySQL 5.5+ 才支持
SIGNAL,低版本只能靠写入错误日志 +INSERT INTO error_log(但不阻断主操作) - PostgreSQL 中
RAISE EXCEPTION会回滚整个事务,如果触发器只是辅助校验,要注意别误伤其他正常更新 - 别在触发器里调用外部 HTTP 请求或写磁盘文件——超时或失败会导致事务卡死或不可预测行为
一致性检查别碰大字段和 JSON,性能会断崖下跌
对 TEXT、MEDIUMTEXT 或 JSON 字段做 OLD.json_col != NEW.json_col,MySQL 实际会做全内容字符串比对;PostgreSQL 的 jsonb 虽优化过,但深度嵌套结构仍可能触发 O(n²) 比较。一次更新牵扯几 MB 数据,延迟直接从毫秒级跳到秒级。
- 优先用业务层生成并校验哈希值(如
SHA2(json_col, 256)),触发器只比对哈希字段 - 如果必须校验 JSON 结构,抽关键路径(如
NEW.json_col->>'$.user.id')而非整树 - 避免在高频更新表(如订单状态表)上加复杂一致性触发器,宁可异步扫表修复,也别堵住写入链路
触发器不是万能的一致性保险丝——它只管单行、单事务内的逻辑,跨表约束、分布式场景、批量导入绕过触发器的情况,都得靠额外机制兜底。最容易被忽略的是:开发时本地测试没问题,上线后高并发下触发器锁表顺序引发死锁,或者 OLD/NEW 引用在分区表里行为异常。这些得真压测才看得见。










