对于几个失败问题,我编写了这个触发器 - audit_failed_trg
作为after servererror on database
触发器。
我的第一个想法是根据需要仅检查特定的异常\用户\表。
但我只是想知道 - 数据库应该为任何故障触发此触发器。
在生产环境中启用它是一个好主意吗?
这是否会导致任何性能问题或其他问题?
我使用的是甲骨文 11g。
create or replace trigger audit_failed_trg
after servererror on database
declare
l_sql_text ora_name_list_t;
l_n number;
begin
insert into T values ( S.NEXTVAL, 1, 'ora_sysevent = ' || ORA_SYSEVENT ,sysdate);
insert into T values ( S.CURRVAL, 2, 'ora_login_user = ' || ORA_LOGIN_USER,sysdate );
insert into T values ( S.CURRVAL, 3, 'ora_server_error = ' || ORA_SERVER_ERROR(1),sysdate );
insert into T values ( S.CURRVAL, 4, 'SID = ' || SYS_CONTEXT ('USERENV','SID'),sysdate);
insert into T values ( S.CURRVAL, 5, 'host = ' || SYS_CONTEXT ('USERENV','HOST') ,sysdate);
insert into T values ( S.CURRVAL, 6, 'ip = ' || SYS_CONTEXT ('USERENV','IP_ADDRESS') ,sysdate);
insert into T values ( S.CURRVAL, 7, 'module = ' || SYS_CONTEXT ('USERENV','MODULE') ,sysdate);
insert into T values ( S.CURRVAL, 8, 'serverhost = ' || SYS_CONTEXT ('USERENV','SERVER_HOST') ,sysdate);
l_n := ora_sql_txt( l_sql_text );
for i in 1 .. l_n
LOOP
insert into t values ( s.CURRVAL,8+i, 'l_sql_text(' || i || ') = ' || l_sql_text(i),sysdate );
end loop;
end;
记录所有服务器错误是个好主意,我已经看到它在生产环境中运行良好。
理论上,错误日志记录由应用程序处理。 实际上,大多数应用程序不会捕获所有数据库错误。 拥有一个包含数据库生成的所有错误的表非常有用。
但是,这种触发器具有需要仔细考虑的特殊挑战:
- 敏感信息- 如果查询具有硬编码的社会保险号,并且查询失败,则该号码将出现在错误日志中。 确保您的组织可以应对该风险。 不要授予每个人访问表的权限。 (并且不要认为应用程序绑定变量可以避免此问题。 最有可能失败的查询是在应用程序外部运行的即席查询,它们将使用硬编码文本。
- 不要责怪别人- 你可能会对错误的数量感到惊讶。 抵制责怪人们产生太多错误消息的冲动。 如果你总是纠缠他们不相关的错误消息,你会激怒很多开发人员。 (这似乎是显而易见的,但我遇到过很多DBA,他们只是喜欢抱怨任何人产生任何错误。 这也是为什么 Oracle 是最讨厌的数据库的原因之一。
-
测试得格外仔细- 构建不佳的系统事件触发器确实会破坏数据库。 最明显的问题是 LOGON 触发器,它可以有效地破坏整个数据库。 在某些方面,
AFTER SERVERERROR
是一个更安全的事件,因为某些东西在调用之前就已经损坏了。 但奇怪的事情仍然可能发生。 例如,您会注意到exception when others then null;
代码。 该代码通常是一种反模式,但这是您真正想要抑制所有异常以避免无限循环的少数几个地方之一。 - 性能- 错误的额外开销无关紧要。 如果您的系统必须针对错误进行优化,那么您会遇到更严重的问题。 但是您可能需要担心桌子的大小。 如果某个进程变得流氓并在一天内发出一百万个无效查询,您不希望它占用大量空间。 (这就是为什么默认情况下,Oracle 不会记录每次执行或每个错误的原因。 拒绝服务攻击的机会太多。
示例架构
花一些时间在更好的桌子设计上。 忽略问题中使用的表的键值对类型。 相反,请创建一个表,该表将为每个错误存储一行,并使用有意义的名称。 像这样的表将更容易有意义地查询:
--drop trigger audit_failed_trg;
--drop table server_errors;
--drop sequence server_error_seq;
create sequence server_error_seq;
create table server_errors
(
id number not null,
error_date date not null,
ora_sysevent varchar2(128),
ora_login_user varchar2(128),
ora_server_error varchar2(4000),
sid number,
host varchar2(256),
ip varchar2(15),
module varchar2(4000),
serverhost varchar2(256),
sql clob,
constraint server_errors_pk primary key(id)
);
触发
create or replace trigger audit_failed_trg
after servererror on database
declare
v_sql_text ora_name_list_t;
v_sql clob;
v_n number;
begin
v_n := ora_sql_txt(v_sql_text);
for i in 1 .. v_n loop
v_sql := v_sql || v_sql_text(i);
end loop;
--If you find a huge number of irrelevant errors, you might want to filter them out here.
insert into server_errors
values
(
server_error_seq.nextval,
sysdate,
ora_sysevent,
ora_login_user,
ora_server_error(1),
sys_context ('USERENV','SID'),
sys_context ('USERENV','HOST'),
sys_context ('USERENV','IP_ADDRESS'),
sys_context ('USERENV','MODULE'),
sys_context ('USERENV','SERVER_HOST'),
v_sql
);
commit;
--Never raise an exception from this trigger.
--No matter what happens we don't want recursive errors.
exception when others then
null;
end;
/