数据仓库项目中使用物化视图推送数据,现记录一下相关方面的知识。
物化视图需要有基表,基表可以在本地库,也可以在远程库。可以在本地通过数据库链接访问远程库的基表
基表:hr.departments@orcl
物化视图:scott.departments@orcl1
在orcl库hr用户上的departments上创建物化视图日志
CREATE MATERIALIZED VIEW LOG ON DEPARTMENTS;
SELECT * FROM ALL_MVIEW_LOGS;
查询数据字典发现生成了一个基于主键的物化视图日志MLOG$_DEPARTMENTS
,结构如下
NAME | TYPE |
---|---|
DEPARTMENT_ID | NUMBER(4) |
SNAPTIME$$ | DATE |
DMLTYPE$$ | VARCHAR2(1) |
OLD_NEW$$ | VARCHAR2(1) |
CHANGE_VECTOR$$ | RAW(255) |
XID$$ | NUMBER |
DEPARTMENT_ID:基表的主键,默认创建的物化视图日志为子句 with primar key,日志表会包含基表的主键列;
SNAPTIME$$:填充刷新时间;
DMLTYPE$$:填充DML操作的类型,I表示插入、D表示删除、U表示更新;
OLD_NEW$$:表示值的新旧,N表示新值,对应插入操作;O表示旧值,对应删除操作;U表示更新值,对应更新操作;
CHANGE_VECTOR$$:表示修改矢量,用来表示被修改的是哪个或哪几个字段 ;
XID$$:基于current scn类型的物化视图日志会用到XID列,该列与current scn 的对应关系存储在视图ALL_SUMMAP中;
创建物化视图日志的标准语句为:
create_materialized_vw_log::=
CREATE MATERIALIZED VIEW LOG
ON [ schema. ] table
[ physical_attributes_clause
| TABLESPACE tablespace
| logging_clause
| { CACHE | NOCACHE }
[ physical_attributes_clause
| TABLESPACE tablespace
| logging_clause
| { CACHE | NOCACHE }
]...
]
[ parallel_clause ]
[ table_partitioning_clauses ]
[ WITH { OBJECT ID
| PRIMARY KEY
| ROWID
| SEQUENCE
| (column [, column ]...)
}
[, { OBJECT ID
| PRIMARY KEY
| ROWID
| SEQUENCE
| (column [, column ]...)
}
]...
[ new_values_clause ]
] ;
physical_attributes_clause::=
[ { PCTFREE integer
| PCTUSED integer
| INITRANS integer
| storage_clause
}
[ PCTFREE integer
| PCTUSED integer
| INITRANS integer
| storage_clause
]...
]
logging_clause::=
{ LOGGING | NOLOGGING }
parallel_clause::=
{ NOPARALLEL | PARALLEL [ integer ] }
new_values_clause::=
{ INCLUDING | EXCLUDING } NEW VALUES
关键字解读
OBJECT ID:则物化视图日志中会包含SYS_NC_OID$,用来记录每个变化行的对象标识符;
PRIMARY KEY:则物化视图日志中会包含主键列;
ROWID:则物化视图日志中会包含M_ROW$$,用来存储发生变化所有行的ROWID;
SEQUENCE:物化视图日志中会包含SEQUENCE$$,用来记录DML操作顺序的编号,保证刷新按顺序刷新,由于无法唯一定位记录,一般不单独使用;
对于关键字INCLUDING NEW VALUES,表示在日志中同时保留新值和旧值,如果此日志的基表的物化视图是单表聚合视图且需要快速刷新,则要指定该关键字;
对于关键字EXCLUDING NEW VALUES,表示禁用在日志中记录新值,以此避免开销;
在orcl1 的scott下创建一个名为departments_1的物化视图,一个名为departments_2的物化视图和一个名为departments_3的物化视图,它们的master table为 orcl库hr用户下的departments
--创建departments_1
CREATE MATERIALIZED VIEW DEPARTMENTS_1
AS
SELECT * FROM HR.DEPARTMENTS@HR_ORCL_LOCALHOST;
--创建departments_2
CREATE MATERIALIZED VIEW DEPARTMENTS_2
AS
SELECT * FROM HR.DEPARTMENTS@HR_ORCL_LOCALHOST;
--创建departments_2
CREATE MATERIALIZED VIEW DEPARTMENTS_2
AS
SELECT * FROM HR.DEPARTMENTS@HR_ORCL_LOCALHOST;
SELECT * FROM DEPARTMENTS_1;
SELECT * FROM DEPARTMENTS_2;
SELECT * FROM DEPARTMENTS_3;
SELECT * FROM ALL_MVIEWS WHERE MVIEW_NAME LIKE 'DEPARTMENTS%';
查询数据字典,发现创建了一个刷新模式为DEMAND 刷新方式为FORCE 的物化视图DEPARTMENTS_1、DEPARTMENTS_2、DEPARTMENTS_3,且两个物化视图的数据量与基表一样。
同时基表所在库将基于departments的所有物化视图最后一次刷新时间记录在数据字典中DBA_BASE_TABLE_MVIEWS
,内容如下
OWNER | MASTER | MVIEW_LAST_REFRESH_TIME | MVIEW_ID |
---|---|---|---|
HR | DEPARTMENTS | 2019/1/9 16:07:01 | 51 |
HR | DEPARTMENTS | 2019/1/9 16:07:06 | 52 |
HR | DEPARTMENTS | 2019/1/9 16:07:12 | 53 |
可以分别与DEPARTMENTS_1、DEPARTMENTS_2、DEPARTMENTS_3相对应起来。
创建物化视图的标准语句详细见oracle 文档,常用语句结构如下:
CREATE MATERIALIZED VIEW [view_name]
[ ON PREBUILT TABLE [ { WITH | WITHOUT } REDUCED PRECISION ]
| [BUILD { IMMEDIATE | DEFERRED }] ]
[REFRESH [FAST|COMPLETE|FORCE] ]
[ ON COMMIT|DEMAND]
[START WITH (start_time) NEXT (next_time) ]
[WITH {PRIMARY KEY | ROWID}]
[ { DISABLE | ENABLE } QUERY REWRITE]
AS subquery
关键字解读
--插入、删除和修改数据
INSERT INTO DEPARTMENTS(DEPARTMENT_ID,DEPARTMENT_NAME,MANAGER_ID,LOCATION_ID)
VALUES(0,'TEST',NULL,1700);
DELETE FROM DEPARTMENTS WHERE DEPARTMENT_ID=1;
UPDATE DEPARTMENTS SET MANAGER_ID=205 WHERE DEPARTMENT_ID=0;
--查询物化视图中的日志记录
select * from MLOG$_DEPARTMENTS;
插入、删除和修改基表之后,基表上的物化视图日志有内容,如下:
DEPARTMENT_ID | SNAPTIME$$ | DMLTYPE$$ | OLD_NEW$$ | CHANGE_VECTOR$$ | XID$$ |
---|---|---|---|---|---|
0 | 4000/1/1 | I | N | FE | 1688948644516339 |
1 | 4000/1/1 | D | O | 00 | 1688948644516339 |
0 | 4000/1/1 | U | U | 08 | 1688948644516339 |
对于物化视图的增量快速刷新来说,要实现对基表数据的同步,需要把基表上的变化信息记录下来,物化视图日志就是这个功能。
在基表上发生DML操作时,内部触发器会将变化信息记录到物化视图日志中,上表展示了对基表进行增删改之后,物化视图日志中记录的数据;
DEPARTMENT_ID:由于默认建立的是基于主键的物化视图日志,所以该列存储了DML操作影响到相关行的主键;
SNAPTIME$$:当DML操作发生后,该字段被填充为日期4000/1/1
,表示这条记录还没有被任何物化视图刷新过。只在第一个物化视图(增量和全量)刷新这条记录时被修改;
DMLTYPE$$:记录下了DML操作的类型,分别I(insert)、D(delete)、U(update),当修改字段为主键时,会用一组(I、D)插入删除操作代替U;
OLD_NEW$$:记录下了值的类型, insert对应N(NEW)、delete对应O(OLD)、update 对应 U(UPDATE);
CHANGE_VECTOR$$:存储以16进制数展示的RAW(255)类型数据,表示改变字段的位置映射,如修改操作对应的位置矢量08
:二进制形式00001000表示,表示改变的列位于表的第三个位置,以此类推,如第一个位置表示为00000010、第二个位置表示为00000100、第一千个位置表示为00000001后面跟一千个0,二进制长度为1008,16进制长度为252;对于删除操作,该列值全为0,个数与列数有关;对于一般插入操作,该列值低位FE,高位补FF:FEFF…,个数与列数有关,对于修改主键而产生的插入操作,该列值全为FF,个数与列数有关;
XID$$:如果创建物化视图日志时指定了WITH COMMIT SCN
,那么日志中就不再包含字段SNAPTIME$$而是记录了修改的XID信息,通过XID和SCN来判断一个物化视图是否需要刷新,由于SCN只能在本地用,所以不支持远程端的物化视图;
--快速增量刷新物化视图
BEGIN
DBMS_MVIEW.refresh('DEPARTMENTS_1','F');
END;
--查询物化视图
SELECT * FROM DEPARTMENTS_1;
观察物化视图的数据,可以发现数据已经和基表同步了,再观察基表上的物化视图日志:
DEPARTMENT_ID | SNAPTIME$$ | DMLTYPE$$ | OLD_NEW$$ | CHANGE_VECTOR$$ | XID$$ |
---|---|---|---|---|---|
0 | 2019/1/9 16:24:23 | I | N | FE | 1688948644516339 |
1 | 2019/1/9 16:24:23 | D | O | 00 | 1688948644516339 |
0 | 2019/1/9 16:24:23 | U | U | 08 | 1688948644516339 |
以及物化视图的最后一次刷新时间:
OWNER | MASTER | MVIEW_LAST_REFRESH_TIME | MVIEW_ID |
---|---|---|---|
HR | DEPARTMENTS | 2019/1/9 16:24:23 | 51 |
HR | DEPARTMENTS | 2019/1/9 16:07:06 | 52 |
HR | DEPARTMENTS | 2019/1/9 16:07:12 | 53 |
当刷新物化视图departments_1后,我们看到 日志中的SNAPTIME$$ 列被更新为刷新时间,且由于在此基表上还有物化视图departments_2没有刷新,所以物化视图中的日志没有被清除。
--快速增量刷新物化视图
BEGIN
DBMS_MVIEW.refresh('DEPARTMENTS_2','F');
END;
--查询物化视图
SELECT * FROM DEPARTMENTS_2;
观察物化视图的数据,可以发现数据已经和基表同步了,再观察基表上的物化视图日志:
DEPARTMENT_ID | SNAPTIME$$ | DMLTYPE$$ | OLD_NEW$$ | CHANGE_VECTOR$$ | XID$$ |
---|---|---|---|---|---|
0 | 2019/1/9 16:24:23 | I | N | FE | 1688948644516339 |
1 | 2019/1/9 16:24:23 | D | O | 00 | 1688948644516339 |
0 | 2019/1/9 16:24:23 | U | U | 08 | 1688948644516339 |
以及物化视图的最后一次刷新时间:
OWNER | MASTER | MVIEW_LAST_REFRESH_TIME | MVIEW_ID |
---|---|---|---|
HR | DEPARTMENTS | 2019/1/9 16:24:23 | 51 |
HR | DEPARTMENTS | 2019/1/9 16:32:36 | 52 |
HR | DEPARTMENTS | 2019/1/9 16:07:12 | 53 |
我们发现,物化视图日志中的的内容在2刷新之后没有删除,没有变化,只是最新的刷新时间被更新了
--快速增量刷新物化视图
BEGIN
DBMS_MVIEW.refresh('DEPARTMENTS_3','F');
END;
--查询物化视图
SELECT * FROM DEPARTMENTS_3;
观察物化视图的数据,可以发现数据已经和基表同步了,
观察物化视图日志,发现记录被清空。
观察物化视图的最后一次刷新时间:
OWNER | MASTER | MVIEW_LAST_REFRESH_TIME | MVIEW_ID |
---|---|---|---|
HR | DEPARTMENTS | 2019/1/9 16:24:23 | 51 |
HR | DEPARTMENTS | 2019/1/9 16:32:36 | 52 |
HR | DEPARTMENTS | 2019/1/9 16:38:51 | 53 |
发现最后物化视图3的最后一次刷新时间更新了。
现总结一下整个实验过程,以解决关于多个物化视图快速刷新时如何根据物化视图日志中的时间戳来确定要更新的记录以及物化视图日志中已经被所有物化视图刷新过的记录如何删除的问题:
4000/1/1
;三个物化视图的最后一次刷新时间大约都是2019/1/9 16:07:06
;SNAPTIME(4000/1/1)>LAST_FRESH_TIME(2019/1/9 16:07:06)
,日志中的三条记录全部被刷新,同时物化视图日志中的三条记录的SNAPTIME被置为2019/1/9 16:24:23
,此时物化视图的最后一次刷新时间: 视图1 是2019/1/9 16:24:23
,2和3没被刷新,还是原来的2019/1/9 16:07:06
。尝试删除日志中的三条记录时判断条件SNAPTIME(2019/1/9 16:24:23)<=基表上所有物化视图的最后刷新时间(1:2019/1/9 16:24:23、2019/1/9 16:07:06 、2019/1/9 16:07:12):不符合条件,没删除日志;SNAPTIME(2019/1/9 16:24:23)>LAST_FRESH_TIME(2019/1/9 16:07:06)
,日志中的三条记录全部被刷新,日志中三条记录的SNAPTIME保持不变,此时物化视图的最后一次刷新时间:视图1 是2019/1/9 16:24:23
,2是2019/1/9 16:32:36
、3没被刷新,还是原来的2019/1/9 16:07:12
。尝试删除日志中的三条记录时判断条件SNAPTIME(2019/1/9 16:24:23)<=基表上所有物化视图的最后刷新时间(1:2019/1/9 16:24:23、2019/1/9 16:32:36 、2019/1/9 16:07:12):不符合条件,没删除日志;SNAPTIME(2019/1/9 16:24:23)>LAST_FRESH_TIME(2019/1/9 16:07:12)
,日志中的三条记录全部被刷新,日志中三条记录的SNAPTIME保持不变,此时物化视图的最后一次刷新时间:视图1 是2019/1/9 16:24:23
,2是2019/1/9 16:32:36
、3是2019/1/9 16:38:51
。尝试删除日志中的三条记录时判断条件SNAPTIME(2019/1/9 16:24:23)<=基表上所有物化视图的最后刷新时间(1:2019/1/9 16:24:23、2019/1/9 16:32:36 、2019/1/9 16:38:51 ):符合条件,删除了三条日志;--ORCL HR
DROP MATERIALIZED VIEW LOG ON DEPARTMENTS;
--ORCL1 SCOTT
DROP MATERIALIZED VIEW DEPARTMENTS_1;
DROP MATERIALIZED VIEW DEPARTMENTS_2;
1.生产环境中创建物化视图时应明确指定查询列,避免基表结构改变导致物化视图失效;
2.主键物化视图的查询子句选用主键列时不能指定函数;
3.物化视图不能包含对LONG和LONG RAW数据类型的引用;
4.等等等。。。。。。。。,没怎么用过