查看Orcale数据里的表是否有变化
由于我们公司一个数据库两个应用在使用,导致一个应用修改了数据库,另一个应用用的缓存而不知道有更新还是原来的结果。原来的处理方式是采用session缓存的方式,用户登出了就清空缓存,这样只需要重新登录一次就得到最新的快照放在缓存中了,但现在新的要求是不登出就要实时刷新改了的内容。其实这种方式最好的处理办法是一个应用改了数据库通知另一个应用去刷新缓存,但是线下应用用vb写的成熟的产品,都是一帮老员工很难让他们去改点东西来适合新应用,都是新应用去套他们的。领导本来说直接不用缓存了,每次去读数据库,我觉得这样解决问题太粗暴了,之前的努力都白费了,就想通过一种查询数据库表是否改变了的方式进行折中处理,在应用里记录一个版本号或者最后修改时间,每次先去数据库读一次看看有没有变化,如果有变化就刷新缓存,如果没有变化就用缓存里的。
我最先想到的是触发器,的确是可以这样实现,但最好不要这样做,因为数据库是另一个项目主要在用,我们不能去过多的改变数据库。我们项目支持的是oracle数据库,user_objects表里有个LAST_DDL_TIME字段,但这个不是我想要的,我想要表DML操作的记录。9i以上可以在user_tab_modifications视图里查找到想要的信息,user_tab_modifications收集自采集信息以来被改变表的dml操作量数据,一个表只有数据量被改变10%以上才会被定期采集信息,也可以执行过程
DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO进行实时采集。
These views are populated only for tables with the MONITORING
attribute. They are intended for statistics collection over a long period of time. For performance reasons, the Oracle Database does not populate these views immediately when the actual modifications occur. Run the FLUSH_DATABASE_MONITORING_INFO
procedure in the DBMS_STATS
PL/SQL package to populate these views with the latest information. The ANALYZE_ANY
system privilege is required to run this procedure.
所以用这种方式是不行的,为了时实还得不断地去执行exec dbms_stats.FLUSH_DATABASE_MONITORING_INFO这个命令,这种方式对数据库操作太多了。
于是有了下面这种方式:ora_rowscn
oracle 10G开始提供的一个伪列ORA_ROWSCN,它又分为两种模式一种是基于block这是默认的模式(块级跟踪);还有一种是基于row上,这种模式只能在建里表时指定ROWDEPENDENCIES(行级跟踪),不可以通过后期的alter table语句来将表修改为ROWDEPENDENCIES。我只需要判断这个表有没有被DML语句操作所以只需要用第一种模式就可以了,不需要精确到row上,所以简单用第一种就可以了。
我们知道,每个Block在头部是记录了该block最近事务的SCN(SystemChangeNumber)的,所以默认情况下,只需要从block头部直接获取这个值就可以了,不需要其他任何的开销,Oracle就能做到这一点。但是这明显是不精确的,一个block中会有很多行记录,每次事务不可能影响到整个block中所有的行,所以这是一个非常不精准的估算值,同一个block的所有记录的ORA_ROWSCN都会是相同的,基本上没有多大的使用价值。
如果在建表的时候开启行级跟踪选项,Oracle则可以为每一行记录精确的SCN,那么显然不能再直接从block头部获取。要获得足够的信息,肯定要付出一定的代价,Oracle必须为每一行存储这个实际的SCN。所以这个行级跟踪的选项,只能在建表的时候指定,而不能通过alter table来修改现有的表,否则需要修改每一行记录的物理存储格式,代价是可想而知的。
在10g之前,很多系统要实现增量数据抽取,要么通过解析日志,要么加触发器,要么就在表上加一个时间截字段。ORA_ROWSCN其实就是第三种方式,只是这个字段由Oracle来维护,这样可以避免一些应用绕过时间截去更新其他字段带来的问题。
第一种方式(块级跟踪):
select ora_rowscn,
dbms_rowid.ROWID_BLOCK_NUMBER(rowid) blockid,
scn_to_timestamp(ora_rowscn)
from hs_futures.fuentrust t
order by scn_to_timestamp(ora_rowscn);
dbms_rowid.ROWID_BLOCK_NUMBER(rowid):是为获取数据所在块的ID
scn_to_timestamp(ora_rowscn):获取数据最所修改的时间
数据发现变化后通过上面SQL语句可以查看到数据最后修改的时间,注意因为是同一个块上,所以这个块上只要有DML操作那么所有数据的scn都更新了,所以凡是跟这条记录在同一个块上数据获取到的ora_rowscn和scn_to_timestamp(ora_rowscn)两个值都发生了相应的变化。
第二种方式(行级跟踪):
create table hs_futures.fuentrust_test1 rowdependencies as
select * from hs_futures.fuentrust
用以上语句创建一个基于ROWDEPENDENCIES模式的表,然后用第一种方法中的SQL去查询此表数据中的ora_rowscn,scn_to_timestamp(ora_rowscn)两个值,修改其中的某一条记录然后再去查询那个值发现发生变化的只是被修改那条记录的这两个值发生了变化,而在同一个块中没有被修改的其它记录这两个值是不会产生变化的。
注意DDL操作:只要现有表记录中的数据有发生变化那么SCN肯定就会发生更新,如删除有数据的列,但是如果索引删除/修改/增加及增加/者删除没有任何数据的列那么SCN是不会有任何变化。
--------------------------------------------------------------
由于我们的用户还有在用9i的数据库,所以放弃了上面的办法,还是采用加触发器的办法。
一开始我想将改变记录写在一个表里,但找了一下项目里没有一个恰当的表来记录这种数据,又不想随便加到一个表里,不就是一个计数器嘛,就想用序列来进行记录。
于是触发器这样写
CREATE OR REPLACE TRIGGER RECORD_DEPT_SCN AFTER INSERT OR UPDATE OR DELETE ON DEPARTMENT DECLARE temp NUMBER; BEGIN SELECT DEPT_SCN.NEXTVAL INTO temp FROM DUAL; END;
在触发器里实现序列的自增,在oracle里我没有找到直接增加序列的方法,只能用这种曲线救国的方式了。(另外有一种直接去改序列底层对应的表sys.seq$的HIGHWATER字段,但这样太有侵入性了,还是算了)
序列这样写
DECLARE exist number; BEGIN SELECT COUNT(1) INTO exist FROM USER_SEQUENCES WHERE SEQUENCE_NAME='DEPT_SCN'; IF exist = 0 THEN EXECUTE IMMEDIATE 'CREATE SEQUENCE DEPT_SCN INCREMENT BY 1 START WITH 1 MAXVALUE 999999999 NOCACHE'; END IF; END;
先判断有这个序列没,如果没有才创建。因为我们的sql在每一次项目启动的时候都会执行,所以必须做判断是否存在。
这里我还学到一个新知识点,就是序列的CACHE,一般默认情况下是20,这个缓存的意义是多个connection来操作这个序列的时候,为了让性能更高,尽量少出现并发争夺,而采用的一种策略,比如cache是20,当前值是0,一个connection来了就把1到20的值全给这个connection用,connection在这个范围内自增序列不用再去访问序列本身了,以此类推另一个connection来了就把21到40给他用,所以这样就效率会提高,序列也会保证递增和唯一,但是不能保证连续,我这个应用想保持连续,而且使用频率是很少的,所以不担心什么性能问题,就不用CACHE了。
另外说一个问题,就是你想调用序列的currval,必须先调用nextval,否则会报ORA-08002: 序列 SEQ1.CURRVAL 尚未在此会话中定义的错误,我想原因就是这个connection没去调用nextval,就是没分配给他一个缓存断,所以必须先调用。那么我想看当前值还得先自增一次,显然不科学,在 USER_SEQUENCES视图里的LAST_NUMBER就是这个系列的nextval的值,但通过SELECT LAST_NUMBER - 1 FROM USER_SEQUENCES WHERE SEQUENCE_NAME = 'DEPT_SCN' 获取的序列当期值可以不让序列自增一次,但是如果你用了CACHA这里获取的LAST_NUMBER其实是给出去缓存数之后下一次再给出缓存数开始的值,比如CACHA是20,你调用了一个nextval,那么去看这个值已经是21了。所以NOCACHE才能保证这里LAST_NUMBER - 1显示的是当前值。
最难的问题解决了,剩下就是在项目里使用了,这个没什么难度就不说了,完成。