Oracle物化視圖的快速刷新機制是通過物化視圖日志完成的。Oracle如何通過一個物化視圖日志就可以支持多個物化視圖的快速刷新呢,本文簡單的描述一下刷新的原理。
首先,看一下物化視圖的結構:
SQL> create table t(id number, name varchar2(30), num number);
表已創建。
SQL> create materialized view log on t with rowid, sequence(id, name) including new values;
實體化視圖日志已創建。
SQL> desc mlog$_t
ID和NAME是建立物化視圖日志時指定的基表中的列,它們記錄每次DML操作對應的ID和NAME的值。
M_ROW$$保存基表的ROWID信息,根據M_ROW$$中的信息可以定位到發生DML操作的記錄。
SEQUENCE$$根據DML操作發生的順序記錄序列的編號,當刷新時,根據SEQUENCE$$中的順序就可以和基表中的執行順序保持一致。
SNAPTIME$$列記錄了刷新操作的時間。
DMLTYPE$$的記錄值I、U和D,表示操作是INSERT、UPDATE還是DELETE。
OLD_NEW$$表示物化視圖日志中保存的信息是DML操作之前的值(舊值)還是DML操作之后的值(新值)。除了O和N這兩種類型外,對於UPDATE操作,還可能表示為U。
CHANGE_VECTOR$$記錄DML操作發生在那個或那幾個字段上。
有關物化視圖日志結構的詳細描述,可以參考文檔:物化視圖日志結構:http://blog.itpub.net/post/468/20498
根據上面的描述,可以發現,當刷新物化視圖時,只需要根據SEQUENCE$$列給出的順序,通過M_ROW$$定位到基表的記錄,如果是UPDATE操作,通過CHANGE_VECTOR$$定位到字段,然后根據基表中的數據重復執行DML操作。
如果物化視圖日志只針對一個物化視圖,那么刷新過程就是這么簡單,還需要做的不過是在刷新之后將物化視圖日志清除掉。
但是,Oracle的物化視圖日志是可以同時支持多個物化視圖的快速刷新的,也就是說,物化視圖在刷新時還必須判斷哪些物化視圖日志記錄是當前物化視圖刷新需要的,哪些是不需要的。而且,物化視圖還必須確定,在刷新物化視圖后,物化視圖日志中哪些記錄是需要清除的,哪些是不需要清除的。
回顧一下物化視圖日志的結構,發現只剩下一個SHAPTIME$$列,那么Oracle如何僅通過這一列就完成了對多個物化視圖的支持呢?下面建立一個小例子,通過例子來進行說明。
使用上文中建立的表和物化視圖日志,下面對這個表建立三個快速刷新的物化視圖。
SQL> create materialized view mv_t_id refresh fast as select id, count(*) from t group by id;
實體化視圖已創建。
SQL> create materialized view mv_t_name refresh fast as select name, count(*) from t group by name;
實體化視圖已創建。
SQL> create materialized view mv_t_id_name refresh fast as select id, name, count(*) from t group by id, name;
實體化視圖已創建。
SQL> insert into t values (1, 'a', 2);
已創建 1 行。
SQL> insert into t values (1, 'b', 3);
已創建 1 行。
SQL> insert into t values (2, 'a', 5);
已創建 1 行。
SQL> insert into t values (3, 'b', 7);
已創建 1 行。
SQL> update t set name = 'c' where id = 3;
已更新 1 行。
SQL> delete t where id = 2;
已刪除 1 行。
SQL> commit;
提交完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
當發生了DML操作后,物化視圖日志中的SNAPTIME$$列保持的值是4000-01-01 00:00:00。這個值表示這條記錄還沒有被任何物化視圖刷新過。第一個刷新這些記錄的物化視圖會將SNAPTIME$$的值更新為物化視圖當前的刷新時間。
SQL> exec dbms_mview.refresh('MV_T_ID');
PL/SQL 過程已成功完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
Oracle根據數據字典中的信息可以知道表T上建立了三個物化視圖,因此,MV_T_ID刷新完之后,不會刪除物化視圖記錄。
Oracle的數據字典中還保存着每個物化視圖上次刷新的時間和當前的刷新狀態。
SQL> select name, last_refresh from user_mview_refresh_times;
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
這些視圖中記錄了每個物化視圖上次執行刷新操作的時間,並且給出每個物化視圖中的數據是否是和基表同步的。由於MV_T_ID剛剛進行了刷新,因此狀態是FRESH,而另外兩個由於在刷新(建立)之后,基表又進行了DML操作,因此狀態為NEEDS_COMPILE。如果這時對基表進行DML操作,則MV_T_ID的狀態也會變為NEEDS_COMPILE。
SQL> insert into t values (4, 'd', 10);
已創建 1 行。
SQL> commit;
提交完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
下面刷新物化視圖MV_T_ID_NAME,刷新操作的判斷依據是,只刷新SNAPTIME$$列大於當前物化視圖的LAST_REFRESH_DATE的記錄,由於物化視圖日志中所有記錄的SNAPTIME$$的值都比物化視圖MV_T_ID_NAME上次刷新的時間點大,因此會刷新所有記錄。對於SNAPTIME$$列的值是4000-01-01 00:00:00的記錄,物化視圖會把SNAPTIME$$列的值更新為當前刷新時間,對於那些已經被更新過的SNAPTIME$$列,則保持原值。
SQL> exec dbms_mview.refresh('MV_T_ID_NAME')
PL/SQL 過程已成功完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
如果這時再次刷新物化視圖MV_T_ID,則只有ID=4的這條記錄的SNAPTIME$$的時間點大於MV_T_ID上次刷新的時間點,因此,只刷新這一條記錄,且不會改變SNAPTIME$$的值。
SQL> exec dbms_mview.refresh('MV_T_ID')
PL/SQL 過程已成功完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
到目前為止,還沒有看到過物化視圖日志的清除,其實每次進行完刷新,物化視圖日志都會試圖刪除沒有用的物化視圖日志記錄。物化視圖日志記錄的刪除條件是刪除那些SNAPTIME$$列小於等於基表所有物化視圖的上次刷新時間。在上面的例子中,由於MV_T_NAME一直沒有刷新,因此它的LAST_REFRESH_DATE比物化視圖日志中所有記錄的值都小,因此,一直沒有發生物化視圖日志記錄清除的現象。
SQL> insert into t values (5, 'e', 2);
已創建 1 行。
SQL> commit;
提交完成。
SQL> exec dbms_mview.refresh('MV_T_NAME')
PL/SQL 過程已成功完成。
SQL> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
物化視圖MV_T_NAME刷新了物化視圖中的每條記錄,更新了ID=5的記錄的SNAPTIME$$時間,並清除了其它所有物化視圖日志記錄。
SQL> drop materialized view log on t;
實體化視圖日志已刪除。
SQL> drop materialized view mv_t_id;
實體化視圖已刪除。
SQL> drop materialized view mv_t_name;
實體化視圖已刪除。
SQL> drop materialized view mv_t_id_name;
實體化視圖已刪除。
SQL> drop table t;
表已刪除。
SQL>
最后,簡單總結一下:
物化視圖在刷新時,會刷新所有SNAPTIME$$大於物化視圖上次刷新時間的記錄,並將所有是4000-01-01 00:00:00的記錄更新為當前刷新時間。對於其他大於上次刷新時間的記錄,只刷新不更改。這樣,當刷新執行完以后,數據字典中記錄當前物化視圖的上次刷新時間為當前時刻,這保證了物化視圖日志中目前所有的記錄都小於或等於刷新時間。因此,每個物化視圖只要刷新大於上次刷新時間的記錄,且保證每次刷新后,所有記錄的時間都小於等於上次刷新時間,那么無論有多少個物化視圖,就可以互不影響的使用同一個物化視圖日志進行快速刷新了。當物化視圖刷新完之后,會清除那些SNAPTIME$$列小於所有物化視圖的上次刷新時間的記錄,而這些記錄已經被所有的物化視圖都刷新過了,保存在物化視圖日志中已經沒有意義了。
-----------------------------------------------------------------------------------
Blog:http://www.cnblogs.com/linjiqin/
J2EE、Android、Linux、Oracle QQ交流群:142463980、158560018(滿)
題外話:
本人來自鐵觀音的發源地——泉州安溪,正宗安溪鐵觀音,有需要的友友歡迎加我Q:416501600。
茶葉淘寶店:http://shop61968332.taobao.com/