小议物化视图与基表数据不一致的消除

小议物化视图与基表数据不一致的消除(一):http://yangtingkun.itpub.net/post/468/326751

小议物化视图与基表数据不一致的消除(二):http://yangtingkun.itpub.net/post/468/327727

小议物化视图与基表数据不一致的消除(三):http://yangtingkun.itpub.net/post/468/490927

这篇文章介绍一下快速刷新的只读物化视图于基表不一致的解决方法。

我并没有碰到过实际的情况,但是在网上看到过几次类似的案例:快速刷新的物化视图与基表出现数据不一致的状况。

因为我并没有在使用中实际碰到过这种情况,因此不好确定问题产生的原因。不过推断产生的原因可能有两种,一个是Oracle的bug造成的,另一个是物化视图的基表被人手工修改。

这里并不打算讨论问题是怎样产生的,而是给出碰到这种问题后的解决方案。

最简单的解决方法:将物化视图执行完全刷新。如果物化视图不是实时访问,可以考虑通过非原子性刷新方式来提高刷新速度。这种方法的优点是简单方便,不会造成错误的结果,风险较小,但是不适用于数据量很大的情况。

第二种方法是重建物化视图。这种方法由于避免了UNDO信息且减少了REDO,效率要比完全刷新高很多。而且也不会造成错误的结果。缺点是物化视图删除后到物化视图重新建立完成直接,这个对象在数据库中不再存在,如果这个时候访问这个对象会直接报错。而且对象重建后,需要恢复物化视图的索引、约束、权限和统计信息等。这些步骤不但麻烦而且容易遗漏。更重要的是,对于数据库很大的表,这种操作的代价仍然是很大的。

最后一种方法也是下面几篇文章的重点,通过手工修改物化视图日志来达到物化视图和基表的一致。这种方法的最大好处是对于数据量很大且不一致的数据量很小的情况,这种方法的代价很小,同步速度也很快。不过这种方法也有很明显的缺点:首先就是方法比较复杂,要求对物化视图的刷新和物化视图日志有比较清晰的了解。而且操作失误的话,很容易造成数据的进一步错误。不过,以前面两种方法作为后盾,即使这种方法真的造成了数据的错误也不会产生太大的问题。

主要讨论如何通过修改物化视图日志来同步INSERT和DELETE操作。

对于物化视图和基表的差别,一共存在三种情况。记录在基表中存在,但是在物化视图中不存在;记录在基表中不存在,但是在物化视图中存在;记录在基表和物化视图中都存在,但是二者不一致。

前面两种情况相当于DELETE和INSERT语句没有应用到物化视图中,而后面一种情况相当于UPDATE语句没有应用到物化视图中。

这里先讨论相对简单的DELETE和INSERT的情况。如果物化视图和基表的数据如下:

SQL> EXEC DBMS_MVIEW.REFRESH('MV_T');

PL/SQL 过程已成功完成。

SQL> SELECT * FROM T;

ID NAME AGE
---------- ------------------------------ ----------
2 TEST 20
3 ABC 15
4 INSERT 30

SQL> SELECT * FROM MV_T;

ID NAME AGE
---------- ------------------------------ ----------
1 DELETE 10
2 TEST 20
3 ABC 15

基表中记录1已经被删除,而物化视图中仍然存在。基表中插入了记录4,而没有同步到物化视图中。

对于上面这两种情况,只需要手工修改物化视图日志就可以实现物化视图数据的同步:

SQL> INSERT INTO MLOG$_T VALUES (4, TO_DATE('4000-1-1', 'YYYY-MM-DD'), 'I', 'N', 'FE');

已创建 1 行。

SQL> INSERT INTO MLOG$_T VALUES (1, TO_DATE('4000-1-1', 'YYYY-MM-DD'), 'D', 'O', '00');

已创建 1 行。

SQL> COMMIT;

提交完成。

第一个字段表示发生变化的记录的ID。

第二个是时间戳填入4000年1月1日,表示任何物化视图还没有对这个记录进行刷新。

第三个字段表示INSERT/UPDATE/DELETE。

第四个字段表示新值(N)/旧值(O)/修改(U)。

最后一个字段表示修改列的矢量。用FE表示INSERT,用00表示DELETE。

关于物化视图日志字段的详细描述可以参考:http://yangtingkun.itpub.net/post/468/20498

关于物化视图是如何利用物化视图日志进行快速刷新的可以参考:http://yangtingkun.itpub.net/post/468/20584

修改MLOG后马上刷新并不会导致数据同步到物化视图:

SQL> EXEC DBMS_MVIEW.REFRESH('MV_T');

PL/SQL 过程已成功完成。

SQL> SELECT * FROM T;

ID NAME AGE
---------- ------------------------------ ----------
2 TEST 20
3 ABC 15
4 INSERT 30

SQL> SELECT * FROM MV_T;

ID NAME AGE
---------- ------------------------------ ----------
1 DELETE 10
2 TEST 20
3 ABC 15

SQL> SELECT * FROM MLOG$_T;

ID SNAPTIME$$ D O CHANGE_VECTOR$$
---------- ---------- - - ----------------------------------------
4 01-1月 -00 I N FE
1 01-1月 -00 D O 00

这是由于Oracle在快速刷新的时候发现基表自上次刷新完成后并未发生修改,因此根本没有去读取物化视图日志。

只要基表数据发生改变,下次物化视图刷新时,会自动将修改同步到物化视图上:

SQL> UPDATE T SET NAME = 'ABC' WHERE ID = 3;

已更新 1 行。

SQL> EXEC DBMS_MVIEW.REFRESH('MV_T');

PL/SQL 过程已成功完成。

SQL> SELECT * FROM T;

ID NAME AGE
---------- ------------------------------ ----------
2 TEST 20
3 ABC 15
4 INSERT 30

SQL> SELECT * FROM MV_T;

ID NAME AGE
---------- ------------------------------ ----------
2 TEST 20
3 ABC 15
4 INSERT 30

通过手工修改物化视图日志,物化视图和基表数据同步完成。需要注意的是,这个方法适用于仅包含一个物化视图的情况或者包含多个物化视图且全部物化视图都出现数据不一致的情况。

对于包含多个物化视图,但是只有个别物化视图数据不一致的情况,需要对这种方法进行适当的改进。这里就不详细介绍了。

也许有人认为有了INSERT和DELETE的方法就不需要UPDATE的方法了,因为只需要在物化视图日志中插入一个DELETE的日志,然后在插入一个INSERT对应的日志信息,Oracle在刷新物化视图的时候,就会将需要UPDATE的记录先删除,然后重新插入。

但是,事情并不那么简单。物化视图的刷新并不仅仅依赖于物化视图日志中的内容,事实上,Oracle会根据物化视图和基表的差异来确定是否执行删除和插入的操作。

关于上面的描述可以参考:物化视图刷新并非完全根据物化视图日志记录:http://yangtingkun.itpub.net/post/468/486248

因此想要通过这种手工添加DELETE和INSERT记录的方式来欺骗Oracle是行不通的。UPDATE操作就只能UPDATE的方式来解决。

虽然无法欺骗Oracle,录音DELETE + INSERT的方式来绕过UPDATE操作,但是在UPDATE操作需要更新的列上可以欺骗一下Oracle。

Oracle处于性能的考虑,当表中不包含LOB列时,虽然物化视图日志明确的记录了哪些列需要被更新,但是Oracle并不会将更新细化到列级,而是表中所有的字段全部更新。这样的话,在处理UPDATE语句的时候就有了省事的办法。在物化视图日志表中手工编辑UPDATE记录的时候,可以在列标识的地方输入任意列,比如’02’。这样就可以实现物化视图和基表之前,同一条记录字段数据不同的同步操作。

SQL> exec dbms_mview.refresh('MV_T')

PL/SQL procedure successfully completed.

SQL> select * from t;

        ID NAME                                  AGE
---------- ------------------------------ ----------
         1 update                                 18
         2 b                                      26
         3 c                                      34
         4 d                                      40

SQL> select * from mv_t;

        ID NAME                                  AGE
---------- ------------------------------ ----------
         1 a                                      18
         2 b                                      26
         3 c                                      34
         4 d                                      40

现在基表和物化视图中ID为1的记录是不同的。现在通过手工添加物化视图日志的方式来实现二者的同步:

SQL> desc mlog$_t                
 Name                                Null?    Type
 ----------------------------------- -------- ---------------
 ID                                           NUMBER
 SNAPTIME$$                                   DATE
 DMLTYPE$$                                    VARCHAR2(1)
 OLD_NEW$$                                    VARCHAR2(1)
 CHANGE_VECTOR$$                              RAW(255)

SQL> insert into mlog$_t values (1, to_date('4000-1-1', 'yyyy-mm-dd'), 'U', 'U', '01');

1 row created.

SQL> col change_vector$$ format a10
SQL> select * from mlog$_t;

        ID SNAPTIME$$     D O CHANGE_VEC
---------- -------------- - - ----------
         1 01-1月 -00     U U 01

SQL> delete t where id = 4;

1 row deleted.

SQL> exec dbms_mview.refresh('MV_T')

PL/SQL procedure successfully completed.

SQL> select * from t;

        ID NAME                                  AGE
---------- ------------------------------ ----------
         1 update                                 18
         2 b                                      26
         3 c                                      34

SQL> select * from mv_t;

        ID NAME                                  AGE
---------- ------------------------------ ----------
         1 update                                 18
         2 b                                      26
         3 c                                      34

第一个字段表示发生变化的记录的ID。

第二个是时间戳填入4000年1月1日,表示任何物化视图还没有对这个记录进行刷新。

第三个字段表示INSERT/UPDATE/DELETE。

第四个字段表示新值(N)/旧值(O)/修改(U)。

最后一个字段表示修改列的矢量。由于UPDATE的时候会更新所有的非LOB列,因此这里并不需要对应到列上,比如这里插入的’01’实际上指向的是主键列。

关于物化视图日志字段的详细描述可以参考:http://yangtingkun.itpub.net/post/468/20498

关于物化视图是如何利用物化视图日志进行快速刷新的可以参考:http://yangtingkun.itpub.net/post/468/20584

修改MLOG后马上刷新并不会导致数据同步到物化视图,这是由于Oracle在快速刷新的时候发现基表自上次刷新完成后并未发生修改,因此根本没有去读取物化视图日志。

因此在上面的例子中对基表数据进行修改,使得物化视图刷新时,会读取手工添加的物化视图日志信息。

可以看到,通过手工修改物化视图日志的方式,使得Oracle的基表和物化视图日志实现了同步。

 

 

猜你喜欢

转载自blog.csdn.net/haponchang/article/details/88572511