各数据库需通过显式透传会话变量获取真实用户:SQL Server用SESSION_CONTEXT()(非APP_NAME()),PostgreSQL用SET LOCAL+current_setting(),MySQL用SET @var或显式字段;均不可依赖USER()/CURRENT_USER(),且必须每次请求开头重设。

不能直接读取客户端真实身份,只能通过显式透传的会话变量或连接属性获取有限上下文;硬靠USER()、CURRENT_USER()这类函数几乎必然失效。
SQL Server:用 SESSION_CONTEXT() 而不是 APP_NAME() 做业务用户标识
APP_NAME() 依赖连接字符串里写死的 Application Name,开发工具不填就为空,且可被伪造;真正可控的是 SESSION_CONTEXT() ——它由应用在每次请求开始时主动设置,触发器内安全读取。
- 应用层必须执行:
EXEC sp_set_session_context @key = N'user_id', @value = 'u123'(注意:每个新连接都要重设) - 触发器内读取:
SESSION_CONTEXT(N'user_id'),返回sql_variant,需显式转换如CAST(SESSION_CONTEXT(N'user_id') AS VARCHAR(50)) - 别用
CONTEXT_INFO():它只能存 128 字节二进制,且多个并发请求可能覆盖,SESSION_CONTEXT()是键值对、线程安全 - 若未设置 key,
SESSION_CONTEXT()返回 NULL,加ISNULL(..., 'unknown')避免空值报错
PostgreSQL:用 current_setting() + SET LOCAL 绑定事务级上下文
PostgreSQL 的 current_setting() 是唯一可靠路径,但必须配合 SET LOCAL 在事务开头设置,且连接池模式要匹配。
- 应用执行:
SET LOCAL app.user_id = 'u123'(LOCAL确保只在当前事务生效) - 触发器内读取:
current_setting('app.user_id', true)——第二个参数true表示“找不到时不报错,返回 NULL” - 若用 PgBouncer,必须确认其运行在
transaction模式,session模式下SET LOCAL不生效 - 变量名不能含空格或特殊字符,推荐
app.user_id或tenant.id这类点分命名
MySQL:只能靠应用传参或会话变量 @var,USER() 和 CURRENT_USER() 完全不可信
USER() 返回连接时声明的账号(如 app@10.2.3.4),CURRENT_USER() 返回认证账号(如 root@localhost),两者在触发器中都固定为定义者,跟真实操作人无关。
- 可行方案一:应用在 INSERT/UPDATE 语句里显式带字段,如
INSERT INTO orders (..., created_by) VALUES (..., 'u123') - 可行方案二:应用连接后立即执行
SET @current_user_id = 'u123',触发器内直接引用@current_user_id - 别试
information_schema.PROCESSLIST:触发器内无法访问该视图,权限和可见性都不保证 - 连接复用是最大坑:如果连接池没做隔离,上一个请求设的
@current_user_id可能被下一个请求误读
所有数据库共通陷阱:别在触发器里查动态管理视图或系统表
试图在触发器内执行 SELECT program_name FROM sys.dm_exec_sessions WHERE session_id = @@SPID(SQL Server)或类似查询,99% 会失败或返回错误结果。
- 触发器运行在语句上下文中,
sys.dm_exec_sessions等视图可能不可见,或因权限不足报错 - 即使查到,
program_name也常是sqlservr.exe或泛化名(如容器主机名),无法区分具体操作人 - 高并发下会话状态瞬变,
@@SPID对应的记录可能已被覆盖或清理 - 真正需要细粒度审计(IP、客户端版本、完整 SQL)的场景,得靠扩展事件(SQL Server)、Audit Log(MySQL 企业版)或 pgAudit(PostgreSQL)——它们在触发器之外捕获,再写入日志表供关联
最易被忽略的一点:上下文变量必须在每次数据库请求**开头**设置,而不是只在登录时设一次;连接复用、中间件透传失败、事务嵌套未重设,都会导致触发器读到过期或错误的值。

















