应通过应用层显式传参(如pg的set local、mysql的用户变量、sql server的context_info)向触发器注入当前业务用户id,再结合行级归属字段(如owner_id)做等值校验,而非依赖数据库内置用户函数。

触发器里拿不到当前用户怎么办
多数数据库的触发器执行时,SESSION_USER、CURRENT_USER 或 USER() 确实可用,但行为差异大:PostgreSQL 的 current_user 是权限角色,不是连接用户;MySQL 的 USER() 返回「host」部分(如 'app@10.2.3.4'),直接解析不可靠;SQL Server 的 SUSER_SNAME() 可用,但需确保登录名与业务用户一致。更稳的方式是让应用层显式传参——通过 SET LOCAL(PG)、SET @user_id(MySQL)或上下文变量(SQL Server 的 CONTEXT_INFO)注入当前业务用户标识,并在触发器中读取。
常见错误现象:INSERT 触发器里查 current_user 得到 'app_rw',结果所有行都放行,实际应校验具体员工 ID。
- PostgreSQL:应用执行
SET LOCAL app.current_user_id = 'u123',触发器用current_setting('app.current_user_id', true) - MySQL:应用执行
SET @current_user_id = 'u123',触发器用@current_user_id - SQL Server:应用执行
SET CONTEXT_INFO 0x75313233(ASCII u123 的十六进制),触发器用CONVERT(VARCHAR(128), CONTEXT_INFO())
INSERT/UPDATE 触发器中校验行级权限的写法
核心逻辑不是“查用户有没有权限”,而是“这条记录的归属字段是否匹配当前用户”。比如订单表有 owner_id 字段,触发器必须确保 NEW.owner_id = 当前用户ID,否则 RAISE EXCEPTION(PG)或 SIGNAL SQLSTATE '45000'(MySQL)中断操作。
容易踩的坑:只校验 UPDATE 的 NEW.owner_id,却忽略 OLD.owner_id —— 用户不能擅自把别人订单转给自己,所以更新时还需判断 OLD.owner_id = 当前用户ID 才允许改其他字段。
- INSERT:只检查
NEW.owner_id = 获取到的当前用户ID - UPDATE:同时检查
OLD.owner_id = 当前用户ID(保证原属自己)和NEW.owner_id是否被非法修改(若业务不允许转交,就禁止改该字段) - DELETE:只需检查
OLD.owner_id = 当前用户ID,不涉及NEW
为什么不能在 BEFORE 触发器里查权限表
触发器里做 SELECT 查询权限配置(比如从 user_role_mapping 表查某用户是否有某行的 view 权)看似灵活,实则危险:它引入了额外 I/O 和锁竞争,尤其在高并发 INSERT 场景下,可能因锁住权限表导致全库阻塞。更重要的是,权限逻辑一旦复杂(如“部门主管可看本部门所有订单”),触发器内联查会迅速失控,且无法利用查询缓存或索引优化。
真正可行的做法是把权限规则“物化”到数据结构里:用冗余字段(如 tenant_id、dept_id、owner_id)承载归属关系,触发器只做等值比对。复杂规则交给应用层或视图处理,触发器只守最后一道门。
触发器权限控制的边界在哪
触发器能拦住 DML,但拦不住 TRUNCATE(PG/MySQL 中不走触发器)、拦不住 ALTER TABLE、拦不住 DBA 直连绕过应用连接池。它也不是替代行级安全策略(RLS)的方案——PostgreSQL 的 ROW SECURITY POLICY 是声明式、自动生效的,而触发器是命令式、易遗漏的。
真正该用触发器的场景只有两个:一是旧系统无法升级到支持 RLS 的数据库版本;二是权限规则必须耦合写入时的业务逻辑(例如“创建工单时自动分配给当前用户,且不允许指定他人”)。除此之外,优先用 RLS、应用层鉴权或视图过滤。










