WHEN子句在触发前过滤70%以上无效行,由执行引擎预判跳过函数调用;仅支持简单比较,不支持子查询、函数、NULL安全比较需显式处理,且语句级触发器不可用。

WHEN子句在触发前就过滤掉70%以上无效行
PostgreSQL 的 WHEN 子句不是“函数里加个if”,而是由执行引擎在调用触发器函数前就完成的谓词判断。只要条件不满足,整行根本不会进入触发器函数——连 PL/pgSQL 环境都不初始化,更不会产生函数栈、变量解析、WAL 日志等开销。
- 合法写法:
WHEN (NEW.status != OLD.status),简单比较,零额外开销 - 非法写法:
WHEN (SELECT is_active FROM users WHERE id = NEW.user_id),语法报错,WHEN不支持子查询 - 批量更新 10,000 行时,若只有 2,800 行状态实际变化,
WHEN可直接跳过其余 7,200 次函数调用
BEFORE 触发器 + WHEN + 直接赋值,避免 UPDATE 递归和锁表
很多人想“自动更新时间戳”,却在 BEFORE UPDATE 触发器里写 UPDATE orders SET updated_at = NOW() WHERE id = NEW.id——这会触发自身再次调用、锁表、违反单表限制,还绕过 WHEN 过滤。
- 正确做法:在
BEFORE UPDATE中直接赋值NEW.updated_at := NOW(); - 必须搭配
WHEN (OLD.updated_at IS NULL OR OLD.updated_at < NOW() - INTERVAL '1 second')避免无意义重写 - 最后别忘
RETURN NEW;,否则修改丢失(PostgreSQL 15 仍要求显式返回)
WHEN 不等于校验,它只是第一道开关
WHEN 能拦住明显不相关的行,但无法替代业务逻辑校验。比如你想阻止 status 从 'draft' 跳到 'archived',WHEN 只能筛出 status 变更的行,真正的合法性检查必须放在触发器函数体里。
-
WHEN无法处理 NULL:NEW.status = 'completed'在NEW.status为 NULL 时结果是 UNKNOWN,整行被跳过;需显式写成NEW.status IS NOT DISTINCT FROM 'completed' - 复杂逻辑(如查配置表、调用校验函数)必须放函数体内,但代价是:哪怕最终
RETURN NULL,函数也已调用一次 -
WHEN不支持CURRENT_USER、now()等函数(少数稳定函数虽被允许,但行为不可控,建议避开)
语句级触发器不能用 WHEN,别混用粒度
FOR EACH STATEMENT 触发器没有 NEW 或 OLD,所以 WHEN (NEW.status = 'done') 会直接报错:ERROR: transition tables not allowed in statement-level triggers。语句级触发器靠的是整体操作发生与否来决定是否执行,不是靠行数据过滤。
- 想统计批量更新影响了多少行?用
AFTER UPDATE ... FOR EACH STATEMENT REFERENCING NEW TABLE AS new_rows,再在函数里SELECT COUNT(*) FROM new_rows - 语句级触发器末尾必须
RETURN NULL,写RETURN NEW会建触发器失败 - 如果你把行级触发器代码复制过去改写成语句级,忘了删
RETURN NEW,就会卡在语法验证阶段
真正省开销的地方,从来不是“函数里少写几行if”,而是让数据库压根不进那个函数。WHEN 子句的位置、语法限制、与触发时机的配合,每一步都得踩准——错一处,70% 的收益就没了。


















