这篇文章源于在PL/SQL领域非常有建树的专家:Steven Feuerstein 的推荐。

        于是突发奇想,将该篇文章翻译成了中文。如果通过我的努力,确实让一些朋友有所收获,我将非常荣幸。

        文章开始:

        之前我的一些文章中,曾经建议通过使用11g中提供的FLASHBACK的数据归档功能而直接替换日志信息表(journalling tables)。即我们已经不再需要针对某些专门表中的记录,我们需要持续跟踪记录所有的改变和被改变之前的记录状态(可以参考如下链接 http://technology.amis.nl/blog/2453/oracle-11g-total-recall-flashback-in-the-hands-of-database-designers-and-application-developers-at-last-and-the-end-of-journalling-tables)。通过Flashbck的数据归档功能不但可以减少编程工作,而且通过该功能可以大幅提升DML操作的性能,同时又可获得许多的方便的功能。例如:Flashback Queries可以直接地象SQL查询一样查询历史数据,甚至通过使用dbms_flashback包,我们可以使用相同的应用程序和相同的查询操作去检索过去某个时间点的数据。

        在之前的不太长的时间,Flashback有许多功能上的限制,而使得其基本上不能记录那些已经存有历史数据但结构又发生变化的表。随着11GR2在这些方面作的改进,绝大多数的限制都已经取消了,并且象增加、重命名以及删除字段和限制这样的对于表的DDL操作以已经可以实现。

        另外一个导致日志表可以消失的原因,就是通过Flashback的Flashback Queries操作,可以得到一些重要的附加信息。何时(DML操作的时间戳)和操作信息(操作类型和原数据信息),这些信息可以很好的知道,操作者到底导致了怎样的变化。而这些在一个部门内的监管是非常重要的。过去,习惯使用数据库用户就是真实用户去提交交易。但是当今业界使用多层服务组件和WEB应用通过池化连接数据库,而造成数据库用户往往不是操作的真实用户。对于最终用户的操作通常(或者应该这样)通过sys_context(‘userenv’, ‘client_identifier’)可以获得,通过设置dbms_session.set_identifier() 或者直接使用连接(例如:JDBC)。对于我们的日志而言,我们需要记录或筛选真实的执行人和客户身份。

        这篇文章描述了记录和后督交易时客户身份信息的方法。

       标准的数据库审计工具(Standard Database Audit Facilities)  

       象其他一些人一样,我也不得不使用,我不甚熟悉的ORACLE数据库自带的审计功能。但让使用不够深入。但是,当试图检查过去某些数据库交易信息的时候,总是能够自然的想到使用使用审计功能。并且这种方法确实让我得到了我需要的信息:每个交易的操作记录,并且通过交易号(transactionid)可以关联到闪回的历史数据查询。这些数据会包含用户身份和执行的所有操作。Oralce数据库的审计可以进行许多种不同操作。从每种对象类型的建立、授权更新和执行操作,到每个数据库用户执行的操作甚至非法的对象。但是因为审计会产生大量的记录数据,而致使我们不得不定期清理这些历史数据。通常这些周期只能都是非常短暂的。为了记录交易历史,我尝试通过日志表转而使用审计功能。并且我需要确定审计的等级--那些操作和那些对象(是否还包含用户?)现在,我需要使用FLASHBAKC功能协助,就像我参考的那些文档,并且我需要确保每个针对EMP表的DML操作都必须被记录。这就意味着,下面的SQL开启审计功能:        

  1. AUDIT INSERTUPDATEDELETE ON SCOTT.EMP 

        为了审计功能,我假设一个web应用,这个应用可以让真是的最终用户修改数据。在数据库中执行如下操作:设置用户身份,应用程序需要执行:        

  1. begin 
  2.   dbms_session.set_identifier('TheRealUser'); 
  3. end

         进行下一个DML操作:

  1. update emp 
  2. set      sal = sal + 100 
  3. where  job ='CLERK' 

         当交易被提交后,EMP使用了新的值,而原有的值成为了历史数据。这些历史数据,可以通过Flashback Versions Query被我们查询。

  1. select ename 
  2. ,      sal 
  3. ,      versions_xid 
  4. ,      VERSIONS_STARTTIME 
  5. ,      VERSIONS_ENDTIME 
  6. from   emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE 
  7. where  job = 'CLERK' 

         结果集如下:

  1. ENAME      SAL    VERSIONS_XID     VERSIONS_STARTTIME           VERSIONS_ENDTIME 
  2. ---------- ------ ---------------- -------------------------    ------------------------- 
  3. ADAMS      1405   07001E00A9040000 06-FEB-11 08.12.09.00 AM     06-FEB-11 08.42.13.00 AM 
  4. ADAMS      1505   04000E0088040000 06-FEB-11 08.42.13.00 AM 
  5. ADAMS      1400                                                 06-FEB-11 08.12.09.00 AM 
  6. JAMES      1255   07001E00A9040000 06-FEB-11 08.12.09.00 AM     06-FEB-11 08.42.13.00 AM 
  7. JAMES      1355   04000E0088040000 06-FEB-11 08.42.13.00 AM 
  8. JAMES      1250                                                 06-FEB-11 08.12.09.00 AM 
  9. MILLER     1605   07001E00A9040000 06-FEB-11 08.12.09.00 AM     06-FEB-11 08.42.13.00 AM 
  10. MILLER     1705   04000E0088040000 06-FEB-11 08.42.13.00 AM 
  11. MILLER     1600                                                 06-FEB-11 08.12.09.00 AM 
  12. SMITH      1105   07001E00A9040000 06-FEB-11 08.12.09.00 AM     06-FEB-11 08.42.13.00 AM 
  13. SMITH      1205   04000E0088040000 06-FEB-11 08.42.13.00 AM 
  14. SMITH      1100                                                 06-FEB-11 08.12.09.00 AM 

        很显然,这些记录的数据中没有修改数据人的信息。但是,配合之前开启的审计追踪功能,我可以关联Flashback的Versions Query与 USER_OBJECT_AUDIT试图,找到更多的执行人(WHO)的数据信息:

  1. select ename 
  2. ,      sal 
  3. ,      uat.client_id 
  4. ,      uat.os_username 
  5. ,      uat.userhost 
  6. ,      uat.username 
  7. ,      uat.scn 
  8. ,      versions_xid 
  9. ,      VERSIONS_STARTTIME 
  10. ,      VERSIONS_STARTSCN 
  11. ,      VERSIONS_ENDTIME 
  12. ,      VERSIONS_ENDSCN 
  13. from   emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE eh 
  14.        join 
  15.        user_audit_trail uat 
  16.        on ( eh.versions_xid = uat.transactionid) 
  17. where  job = 'CLERK' 
  18. order 
  19. by     ename, versions_starttime 

        结果集显示客户身份(client identifier)信息与实际的操作用户不符。

  1. ENAME  SAL  CLIENT_ID    OS_USERNAME USERHOST USERNAME SCN      VERSIONS_XID     VERSIONS_STARTTIME       VERSIONS_STARTSCN 
  2. ---------- ---------------------- ---------------------------------------------------------------- ------------------------ 
  3. ADAMS  1505 TheRealUser  demo        xp-vm    SCOTT    2200769  04000E0088040000 06-FEB-11 08.42.13.00 AM 2200778 
  4. JAMES  1355 TheRealUser  demo        xp-vm    SCOTT    2200769  04000E0088040000 06-FEB-11 08.42.13.00 AM 2200778 
  5. MILLER 1705 TheRealUser  demo        xp-vm    SCOTT    2200769  04000E0088040000 06-FEB-11 08.42.13.00 AM 2200778 
  6. SMITH  1205 TheRealUser  demo        xp-vm    SCOTT    2200769  04000E0088040000 06-FEB-11 08.42.13.00 AM 2200778 

        注意:如果我想通过flash的versions query找到开启审计之前的历史数据,需要使用左外联接在我们的查询中。        

  1. select ename 
  2. ,      sal 
  3. ,      uat.client_id 
  4. ,      uat.os_username 
  5. ,      uat.userhost 
  6. ,      uat.username 
  7. ,      uat.scn 
  8. ,      versions_xid 
  9. ,      VERSIONS_STARTTIME 
  10. ,      VERSIONS_STARTSCN 
  11. ,      VERSIONS_ENDTIME 
  12. ,      VERSIONS_ENDSCN 
  13. from   emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE eh 
  14.        left outer join 
  15.        ( select uat.* 
  16.          ,      row_number() over (partition by transactionid order by extended_timestamp) rn 
  17.          from   user_audit_trail uat 
  18.         ) uat 
  19.        on ( eh.versions_xid = uat.transactionid) 
  20. where  job = 'CLERK' 
  21. and    rn = 1 
  22. order 
  23. by     ename, versions_starttime 

        现在,我已经介绍了一个嵌套SQL,如何通过一个查询将user_audit_trail所有交易都查询出来,而不是通过一个交易一个查询的方式进行检索。

        这个方法确实非常简单。因为,我们可以象轻松开启审计信息那样开启每张表的Flashback功能。

        但是这里却有一个审计开销缺点。首先是性能问题,因为对于每条被“触碰”到的记录,都会触发日志信息记录,同时存储在系统审计表SYS.AUD$中的数据量将是非常惊人的。因为审计将会收集所有用户和对象的操作。审计信息存储表需要周期性清除。注意,清理可以有选择行的指定某个对象或者操作。

        为了简化标准的审计跟踪功能,我们可以构建一个JOB,周期性地从 sys.aud$表中抽取我们需要的记录信息,并构建自己需要的交易历史。因为审计机制会完整的记录每个交易的DML操作语句但我们只是需要每个交易的单个记录,我们可以通过整合针对每个交易记录的审计信息,达到大幅降低交易历史数据量的目的。交易历史表可以象如下:

  1. create table transaction_history 
  2. ( transaction_id    varchar2(100) not null 
  3. , client_identifier varchar2(200) 
  4. , os_user           varchar2(200) 
  5. , scn               number 
  6. , transaction_start_timestamp     timestamp default systimestamp 
  7. , db_user           varchar2(100) 
  8. , application_info  varchar2(2000) 

       从审计跟踪表中抽取交易历史数据操作:

  1. insert into transaction_history 
  2. ( transaction_id 
  3. , client_identifier 
  4. , os_user 
  5. , scn 
  6. , transaction_start_timestamp 
  7. , db_user 
  8. select uat.transactionid 
  9. ,      uat.client_id 
  10. ,      uat.os_username 
  11. ,      uat.scn 
  12. ,      uat.extended_timestamp 
  13. ,      uat.username 
  14. from   ( select uat.* 
  15.          ,      row_number() over (partition by transactionid order by extended_timestamp) rn 
  16.          from   user_audit_trail uat 
  17.         ) uat 
  18. where  rn = 1 
  19. and    transactionid is not null 

        对于审计跟踪(可能需要部分清理)和交易历史表(可能不能全部更新),我们可以将两者结合来构建基于EMP表的交易历史记录。

  1. select ename 
  2. ,      sal 
  3. ,      versions_xid 
  4. ,      nvl(th.client_identifier, uat.client_id) client_id 
  5. ,      nvl(th.os_user, uat.os_username) os_user 
  6. ,      uat.userhost userhost 
  7. ,      nvl(th.db_user, uat.username) db_user 
  8. ,      nvl(th.scn,uat.scn) scn 
  9. ,      VERSIONS_STARTTIME 
  10. ,      VERSIONS_STARTSCN 
  11. ,      VERSIONS_ENDTIME 
  12. ,      VERSIONS_ENDSCN 
  13. from   emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE eh 
  14.        left outer join 
  15.        ( select uat.* 
  16.          ,      row_number() over (partition by transactionid order by extended_timestamp) rn 
  17.          from   user_audit_trail uat 
  18.         ) uat 
  19.        on ( eh.versions_xid = uat.transactionid) 
  20.        left outer join 
  21.        transaction_history th 
  22.        on ( eh.versions_xid = th.transaction_id) 
  23. where  job = 'CLERK' 
  24. and    uat.rn = 1 
  25. order 
  26. by     ename, versions_starttime 

        注意:查询可以部分的结合审计跟踪信息和我们自己的交易历史表在一个试图中。

        上面提及的大概方法给我们提供一个针对标准应用构建日志表的思路。在这个举例中,我们并没有建立触发器。简单地开启针对某些表DML操作的审计跟踪,配以这些表的flashback归档数据,就足以构建我们的日志信息。在后续的文章中将会更为详细的介绍。

        当然,如果我们要实际构建这样的日志信息也许还有一个难点需要克服,即关于DBA的审计,尤其是对于标准审计特性,对于DBA和对于应用开发人员的日志记录,我们的应用往往是没有考虑的(但是,我们需要他或她也去建立flashback的归档数据)。

        在未来的文章中,我将会讨论看似相似的问题:跟踪交易历史的方法和通过flashback归档数据收集全面的日志信息。

        ----EOF 译文完。

        原文链接:

        http://technology.amis.nl/blog/10911/database-transaction-recorder-adding-who-to-when-and-what-to-make-flashback-take-over-from-journalling-tables

        Thanks Lucas Jellema -:)