触发器不能用于查询时动态脱敏,仅支持insert/update/delete,无法响应select或按角色差异化返回;真正可行的是rls+视图结合current_user与case when实现运行时字段脱敏。

不能靠触发器做查询时的动态脱敏,它只在 INSERT/UPDATE 时生效,且无法按用户角色差异化返回结果。 触发器适合写入阶段的强制清洗(比如入库前统一格式化手机号、抹掉空格、校验长度),但解决不了“DBA 看明文、开发看星号”这类动态需求。
触发器只能改写写入数据,不是查询拦截器
PostgreSQL 的 TRIGGER 是在 DML 执行过程中介入的机制,它能修改即将插入或更新的 NEW 行,但对 SELECT 完全无感知。常见误用是想让它“看到 SELECT 就自动替换字段值”,这在 PG 原生中做不到。
- 触发器执行时机只有
BEFORE INSERT、BEFORE UPDATE、AFTER INSERT等,没有BEFORE SELECT - 即使你在
BEFORE INSERT里用CASE WHEN清洗NEW.phone,后续SELECT仍返回清洗后的值——所有人看到的都一样 - 若想实现“不同角色看到不同内容”,必须用
RLS(行级安全策略)+VIEW或anonymizer扩展,而不是触发器
适合触发器做的脱敏:入库前结构归一化
真正该用触发器的场景,是防止脏数据进入数据库,比如手机号带空格、身份证大小写混用、邮箱全小写但业务要求统一格式。这类处理必须在写入时完成,否则后续所有逻辑都要重复判断。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- 用
TRIM()+UPPER()+REGEXP_REPLACE()统一清洗输入值 - 对空值或非法长度字段设默认值(如
NULL→'UNKNOWN'),避免后续CASE每次都判空 - 示例:入库前标准化手机号(仅保留数字,不足11位补 NULL)
CREATE OR REPLACE FUNCTION normalize_phone()
RETURNS TRIGGER AS $$
BEGIN
NEW.phone := CASE
WHEN NEW.phone IS NULL THEN NULL
ELSE REGEXP_REPLACE(TRANSLATE(NEW.phone, ' -()+', ''), '\D', '', 'g')
END;
IF LENGTH(COALESCE(NEW.phone, '')) != 11 THEN
NEW.phone := NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_normalize_phone
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION normalize_phone();
为什么别在触发器里做“掩码式脱敏”?
把脱敏逻辑塞进触发器(比如自动把 NEW.id_card 改成 '110101******1234')看似省事,实际埋了三个雷:
-
UPDATE后原始值永久丢失,审计、合规回溯、下游同步全部失效 - 触发器无法区分“谁在写”,管理员和普通用户写入同一条记录,都会被强制脱敏
- 一旦脱敏规则变更(如从保留前6后4改成前4后4),历史数据无法批量重跑,因为触发器不作用于已有行
替代方案:用 RLS + VIEW 实现真·动态脱敏
如果目标是“查的时候才脱敏”,直接跳过触发器,走 PostgreSQL 原生支持的组合:
- 建一个视图
v_users_safe,里面用CASE WHEN current_user = 'dev' THEN ... ELSE phone END控制显示逻辑 - 配
RLS策略禁止用户直查原表:ALTER TABLE users ENABLE ROW LEVEL SECURITY; CREATE POLICY deny_all ON users TO PUBLIC USING (false); - 给角色授视图权限:
GRANT SELECT ON v_users_safe TO dev_role; - 注意:视图里不能用
current_role判定(它返回的是当前会话角色,不是登录用户),要用current_user或session_user
最易被忽略的一点:所有脱敏逻辑必须显式处理 NULL、空字符串、超长/截断三种边界情况,否则 CASE 分支漏掉就会返回原始值——等于没脱敏。










