INSERT触发器中不能用DEFAULT关键字赋值,必须显式指定值;仅BEFORE触发器可修改NEW数据;需处理NULL/空字符串边界;性能开销大,高频写入时应谨慎使用。

INSERT 触发器里不能用 DEFAULT 关键字赋值
MySQL 和 PostgreSQL 的 DEFAULT 只在列定义或 INSERT ... VALUES (DEFAULT) 时生效,触发器里写 NEW.column = DEFAULT 会报错(MySQL 报 Unknown system variable 'DEFAULT',PostgreSQL 直接语法错误)。真正可行的是显式赋值——哪怕值和默认值一样,也得手动写出来。
实操建议:
- 先查表结构:
SHOW CREATE TABLE table_name或\d table_name,确认原默认值是常量(如'pending')、函数(如CURRENT_TIMESTAMP)还是表达式 - 如果是常量,默认值直接复用:
NEW.status := 'pending'; - 如果是
CURRENT_TIMESTAMP,PostgreSQL 写NEW.created_at := NOW();,MySQL 写SET NEW.created_at = NOW(); - 别试图在触发器里“继承”默认逻辑——它不存在于运行时上下文
BEFORE INSERT 触发器才能修改 NEW 行数据
只有 BEFORE INSERT(或 BEFORE UPDATE)触发器能安全修改 NEW 中的字段值;AFTER 触发器读不到修改后的结果,更无法反向影响当前插入行为。
常见错误现象:
- 写了
AFTER INSERT然后对NEW.id赋值,实际毫无效果 - 在
BEFORE触发器里漏了FOR EACH ROW,导致整个语句只执行一次,批量插入时只剩最后一行被处理 - PostgreSQL 要求触发器函数返回
NEW,MySQL 则不需要显式返回
示例(PostgreSQL):
CREATE OR REPLACE FUNCTION set_status_by_user_type()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.user_role = 'admin' THEN
NEW.status := 'approved';
ELSE
NEW.status := 'pending';
END IF;
RETURN NEW; -- 这行不能少
END;
$$ LANGUAGE plpgsql;业务条件判断必须覆盖 NULL 和空字符串边界
真实业务字段经常为 NULL 或空字符串,但 IF NEW.category = 'vip' 在 category IS NULL 时既不进分支也不报错,容易静默走默认逻辑,埋下数据一致性隐患。
使用场景:用户注册时根据邮箱域名自动设等级、订单创建时按金额区间设优先级。
实操建议:
- 显式检查空值:
IF NEW.email IS NOT NULL AND NEW.email ~ '@company\.com$'(PostgreSQL 正则) - MySQL 用
IF NEW.amount IS NOT NULL AND NEW.amount > 1000,避免NULL > 1000返回UNKNOWN - 字符串比较前用
TRIM()去首尾空格,防止' vip '匹配失败 - 别依赖客户端传来的“看起来合法”的值——触发器是最后一道防线
触发器性能开销在高频写入时不可忽视
每个触发器都会增加单条 INSERT 的执行时间,尤其当逻辑里包含子查询、函数调用或跨表关联时。QPS 上千的订单表加一个查用户配置表的触发器,可能让平均延迟从 2ms 涨到 15ms。
参数差异与影响:
- MySQL 的触发器无法异步执行,所有逻辑同步阻塞写入
- PostgreSQL 允许触发器调用
pg_notify做轻量通知,但复杂计算仍得同步完成 - 如果业务条件需要查外部 API 或缓存,绝对不要放进触发器——改用应用层预计算 + 字段透传
- 上线前务必用真实数据压测:
INSERT吞吐下降超过 20%,就得重新评估是否值得用触发器
复杂点在于:业务规则看似简单,但一旦涉及多表状态、时间窗口、权限校验,就很容易在触发器里写出隐式锁竞争或 N+1 查询。这时候宁可拆成应用层两步操作,也别硬塞进触发器。

















