能监控,但触发器仅响应DML事件并捕获当前会话上下文,真实用户标识需用数据库特定函数获取(如Oracle的ora_login_user、SQL Server的SUSER_SNAME()),连接池场景下需应用层配合;过滤应在INSERT语句WHERE子句中实现,避免触发器内无效分支;FOR EACH ROW触发器性能开销大,高并发时应优化日志表索引并慎用SQL解析;SET-based操作无法定位单行来源,需应用埋点或CDC补充。

能监控,但必须明确:触发器本身不区分“用户意图”,它只响应 DML 事件并捕获当前会话上下文。真正能拿到的用户标识是 ora_login_user(Oracle)或 SUSER_SNAME()(SQL Server),不是“某人想监控谁”,而是“谁在当前会话执行了操作”。
触发器里怎么拿到执行用户和客户端信息
不同数据库获取方式差异大,硬写错一个函数就查不到真实用户:
- Oracle 中用
ora_login_user拿登录名,sys_context('userenv','ip_address')拿客户端 IP,userenv('SESSIONID')拿会话 ID; - SQL Server 中用
SUSER_SNAME()(Windows 或 SQL 登录名),HOST_NAME()(客户端机器名),APP_NAME()(如 “Microsoft SQL Server Management Studio”); - 注意:如果应用用了连接池(比如 Tomcat + JDBC),所有请求看起来都来自同一个中间件账号,
SUSER_SNAME()会固定为池账号,不是最终操作人 —— 这时得靠应用层传入CONTEXT_INFO或改用 CDC/审计日志。
监控特定用户要过滤 WHERE 条件,别放触发器外层
不能在触发器开头写 IF SUSER_SNAME() != 'admin' THEN RETURN; 就完事 —— 这样虽跳过插入,但触发器仍被调用、仍占资源、仍可能因权限或空值报错。正确做法是把过滤逻辑放在 INSERT 日志表的 VALUES 或 SELECT 子句里:
INSERT INTO dml_log (op_time, op_user, op_type, table_name, sql_text)
SELECT GETDATE(), SUSER_SNAME(), 'UPDATE', 'orders', EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)')
WHERE SUSER_SNAME() IN ('alice', 'bob');
这样既避免无效日志,又减少触发器内分支判断开销。Oracle 同理,INSERT ... SELECT ... FROM dual WHERE ora_login_user IN (...)。
FOR EACH ROW 触发器对性能影响比你想象的大
尤其当监控表高频更新时,每个行变更都进一次触发器,还带 ora_sql_txt() 或 EVENTDATA() 解析,容易拖慢主业务:
-
ora_sql_txt()在 Oracle 中最多返回 30 行 SQL 片段,拼接不当会截断; - SQL Server 的
EVENTDATA()是 XML,每次调用都有序列化开销,且仅在 AFTER 触发器中可用; - 如果只是想记录“谁改了哪条记录”,用
Inserted和Deleted临时表就够了,没必要解析原始 SQL —— 解析 SQL 是为了审计合规,不是为了追踪字段变化; - 高并发场景下,日志表本身可能成瓶颈,建议加索引在
op_user和op_time上,避免全表扫描查询。
最易被忽略的一点:触发器无法捕获 SET-based 操作中的单行来源。比如一条 UPDATE orders SET status='shipped' WHERE user_id IN (SELECT id FROM users WHERE dept='sales'),触发器只知道是当前用户执行了 UPDATE,但不知道具体哪几行对应哪个 sales 用户 —— 这种关联必须靠应用层埋点或改用 CDC+变更表关联实现。

















