前言
现在我们通过一些例子来说明一下MView Log的基本结构以及MView快速刷新的过程。
在这一部分里面,我们还是利用上一部分提供的例子先建立一个MView,同时也创建该MView基表的MView Log。
-- 创建一个测试用的表T
USER@orcl> create table t (a int, b varchar2(50), constraint pk_tprimary key(a));
Table created.
-- 创建对应的MV名为MVT
USER@orcl> create materialized view mvt as select * from t;
Materialized view created.
-- 现在往表里面插入一些个数据
USER@orcl> insert into t select rownum, object_name fromall_objects;
11449 rows created.
USER@orcl> commit;
Commit complete.
-- 下面对mview做一次刷新看看
USER@orcl> exec dbms_mview.refresh('mvt');
PL/SQL procedure successfully completed.
-- 创建MView Log
USER@orcl> create materialized view log on t;
Materialized view log created.
-- 接下来就是进行快速刷新了
USER@orcl> exec dbms_mview.refresh('mvt', 'F');
PL/SQL procedure successfully completed.
MView Log的结构
我们先看一下两个测试表的MView Log的结构:
-- 含有PK的MView
USER@orcl> desc mlog$_t;
Name Null? Type
-------------------- -------- --------------
A NUMBER
SNAPTIME$$ DATE
DMLTYPE$$ VARCHAR2(1)
OLD_NEW$$ VARCHAR2(1)
CHANGE_VECTOR$$ RAW(255)
现在我们挨个说一下各列的含义:
A
这个我们基表T的主键列,如果基表的主键是一个复合索引的话那这里也就存在多个和基表定义一样的列,当基表被修改以后,基表的主键就会被记录到这个列里面。
SNAPTIME$$
用来记录MView刷新的时间,这个字段只有在一个基表对应一个以上的MView的时候才有意义,因为对于一个MView Log来说,只有当所有的MView都刷新完了以后才能把MView Log里面的记录删除,当一个MView刷新的时候,它会把此列置成该MView刷新的时候,在这个MView再次刷新的时候,那些上次刷新过的列就不用再次被刷新了。
下面我们用一个例子来说明一下:
-- 在建立一个基于表T的MView MVT2
USER@orcl> create materialized view mvt2 as select * from t;
Materialized view created.
-- 先做一次刷新
USER@orcl> exec dbms_mview.refresh('mvt2');
PL/SQL procedure successfully completed.
-- 现在对表T做一些修改
USER@orcl> update t set b=upper(b) where rownum<5;
4 rows updated.
-- 现在看看MView Log的记录
USER@orcl> select * from mlog$_t;
A SNAPTIME$$ D O CHANGE_VEC
---------- ------------------- - - ----------
936 4000-01-01 00:00:00 U U 04
937 4000-01-01 00:00:00 U U 04
938 4000-01-01 00:00:00 U U 04
939 4000-01-01 00:00:00 U U 04
-- 现在我们刷新MVT
USER@orcl> exec dbms_mview.refresh('mvt','f');
PL/SQL procedure successfully completed.
-- 我们可以看到SNAPTIME$$时间变了
USER@orcl> select * from mlog$_t;
A SNAPTIME$$ D O CHANGE_VEC
---------- ------------------- - - ----------
936 2009-02-18 07:28:09 U U 04
937 2009-02-18 07:28:09 U U 04
938 2009-02-18 07:28:09 U U 04
939 2009-02-18 07:28:09 U U 04
-- 对比一下MVT的LAST_REFRESH_DATE,我们可以发现这个是一样的
USER@orcl> select MVIEW_NAME, LAST_REFRESH_DATE from dba_mviewswhere mview_name in ('MVT', 'MVT2');
MVIEW_NAME LAST_REFRESH_DATE
------------------------------ -------------------
MVT 2009-02-18 07:28:09
MVT2 2009-02-18 07:27:19
DMLTYPE$$
用于表示DML操作类型,I表示INSERT,D表示DELETE,U表示UPDATE,这个我们可以从上面的例子里面得到验证。
OLD_NEW$$
用于表示这个值是新值还是旧值。N(EW)表示新值,O(LD)表示旧值,U表示UPDATE操作。
CHANGE_VECTOR$$
表示修改矢量,用来表示被修改的是哪个或哪几个字段,用二进制的方式来保存修改列的结果。
USER@orcl> create table ttt (a int, b varchar(40), c varchar(40));
Table created.
USER@orcl> alter table ttt add constraint pk_ttt primary key(a);
Table altered.
USER@orcl> insert into ttt select rownum, object_name, object_namefrom all_objects;
11488 rows created.
USER@orcl> commit;
Commit complete.
USER@orcl> create snapshot mvttt as select * from ttt;
Materialized view created.
USER@orcl> create snapshot log on ttt;
Materialized view log created.
USER@orcl> commit;
Commit complete.
USER@orcl> update ttt set b=upper(b) where rownum<5;
4 rows updated.
USER@orcl> update ttt set c=upper(c) where rownum<5;
4 rows updated.
USER@orcl> update ttt set b=upper(b), c=upper(c) where rownum<5;
4 rows updated.
USER@orcl> select * from mlog$_ttt;
A SNAPTIME$$ D O CHANGE_VEC
---------- ------------------- - - ----------
253 4000-01-01 00:00:00 U U 04
254 4000-01-01 00:00:00 U U 04
255 4000-01-01 00:00:00 U U 04
256 4000-01-01 00:00:00 U U 04
253 4000-01-01 00:00:00 U U 08
254 4000-01-01 00:00:00 U U 08
255 4000-01-01 00:00:00 U U 08
256 4000-01-01 00:00:00 U U 08
253 4000-01-01 00:00:00 U U 0C
254 4000-01-01 00:00:00 U U 0C
255 4000-01-01 00:00:00 U U 0C
256 4000-01-01 00:00:00 U U 0C
从上面的例子我们可以看出,修改列B的CHANGE_VECTOR$$是04(0100),修改列C的CHANGE_VECTOR$$值是08(1000),同时修改这两个列就变成了0C(1100)。
刷新的过程
完全刷新的过程
在Oracle 9i以及以前版本中,MView的完全刷新是先对数据库基表做一个truncate操作然后再将基表的数据全部插入到MView中,而Oracle 10g中,MView完全刷新之前并不对MView进行truncate的操作,取而代之的是delete操作,这个在操作大量的数据的时候会有很大的影响,这一点可以通过开trace来看到,这里就不在贴实验的具体过程了。
快速刷新过程
看了MView Log的结构之后我们很容易就能理解MView快速刷新的基本原理了:首先在对基表做update、delete、insert操作之后,隐藏的触发器会把基表的修改记录到MView Log中,在MView Log中基表的主键会被记录(仅对于基于PK的MView来说,其他类型的MView随后介绍),这个记录的pk会在MView做快速刷新的时候被用来定位被操作的数据行,同时还有一些其他的数据会被记录。
在MView做快速刷新的时候,对于不同的操作语句会有一点点不一样的地方:
insert操作
通过MView Log记录的基表的主键数据以及DMLTYPE$$字段我们知道那些行是行是新插入的,只要将这些新数据导入到MView中即可。
delete操作
同样通过MView Log记录的基表的主键数据以及DMLTYPE$$字段我们知道那些行是行是被删除了的,只要将这些在基表被删除掉的数据在MView中删除掉即可。
update操作
通过MView Log记录的基表的主键,DMLTYPE$$字段以及CHANGE_VECTOR$$我们知道那些行的那些列进行了更新的操作,然后再依照基表的数据对MView中相应的列挨个进行刷新操作。
下面我们利用上面的MVTTT进行一下update操作的实验,主要是验证update的时候Oracle是否只会update基表中被修改的列:
首先我们建立一个对MView MVTTT中列C的触发器,在当C列被修改以后在一个新表中插入一条记录。
-- 创建一个记录表
USER@orcl> create table ttt_t(a timestamp default sysdate, bvarchar(10));
Table created.
-- 创建相应的触发器
USER@orcl>
CREATE OR REPLACE TRIGGER tri_mvttt
BEFORE INSERT OR DELETE OR UPDATE OF c ON ttt
FOR EACH ROW
BEGIN
INSERT INTO ttt_t VALUES(SYSDATE, 'c');
END tri_mvttt;
/
Trigger created.
现在我们对基表的列b做一次更新的操作,看看刷新MView以后表TTT_T中是否有数据
USER@orcl> update ttt set b=upper(b) where rownum<5;
4 rows updated.
USER@orcl> select * from mlog$_ttt;
A SNAPTIME$$ D O CHANGE_VEC
---------- ------------------- - - ----------
253 4000-01-01 00:00:00 U U 04
254 4000-01-01 00:00:00 U U 04
255 4000-01-01 00:00:00 U U 04
256 4000-01-01 00:00:00 U U 04
-- 做一次快速刷新
USER@orcl> exec dbms_mview.refresh('mvttt', 'f');
PL/SQL procedure successfully completed.
-- 我们可以看到TTT_T里面是没有数据的
USER@orcl> select * from ttt_t;
no rows selected
接着我们再对对基表的列c做一次更新的操作,看看刷新MView以后表TTT_T中是否有数据
USER@orcl> update ttt set c=upper(c) where rownum<5;
4 rows updated.
USER@orcl> select * from mlog$_ttt;
A SNAPTIME$$ D O CHANGE_VEC
---------- ------------------- - - ----------
253 4000-01-01 00:00:00 U U 08
254 4000-01-01 00:00:00 U U 08
255 4000-01-01 00:00:00 U U 08
256 4000-01-01 00:00:00 U U 08
-- 做一次快速刷新
USER@orcl> exec dbms_mview.refresh('mvttt', 'f');
PL/SQL procedure successfully completed.
-- 这回我们可以看到数据了
USER@orcl> select * from ttt_t;
A B
------------------------------ ----------
18-FEB-09 01.16.53.000000 PM c
18-FEB-09 01.16.53.000000 PM c
18-FEB-09 01.16.53.000000 PM c
18-FEB-09 01.16.53.000000 PM c
注意:对于基表的每一次update操作都会被记录到MView Log中,不管更新的是否是同一行的同一列,而这些操作也会在MView中被一一的推一遍,一个不拉的,这样如果一个MView操作很频繁而且刷新的间隔太长了的话快速刷新也是很痛苦的一件事的。