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 NUMBER
NAME VARCHAR2(30)
M_ROW$$ VARCHAR2(255)
SEQUENCE$$ NUMBER
SNAPTIME$$ DATE
DMLTYPE$$ VARCHAR2(1)
OLD_NEW$$ VARCHAR2(1)
CHANGE_VECTOR$$ RAW(255)
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
2 select id, count(*) from t group by id;
實體化視圖已建立。
SQL> create materialized view mv_t_name refresh fast as
2 select name, count(*) from t group by name;
實體化視圖已建立。
SQL> create materialized view mv_t_id_name refresh fast as
2 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> select id, name, m_row$$, snaptime$$, dmltype$$ from mlog$_t;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
1 a AAACJEAAFAAAAD4AAA 4000-01-01 00:00:00 I
1 b AAACJEAAFAAAAD4AAB 4000-01-01 00:00:00 I
2 a AAACJEAAFAAAAD4AAC 4000-01-01 00:00:00 I
3 b AAACJEAAFAAAAD4AAD 4000-01-01 00:00:00 I
3 b AAACJEAAFAAAAD4AAD 4000-01-01 00:00:00 U
3 c AAACJEAAFAAAAD4AAD 4000-01-01 00:00:00 U
2 a AAACJEAAFAAAAD4AAC 4000-01-01 00:00:00 D
已選擇7行。
當發生了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;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
1 a AAACJEAAFAAAAD4AAA 2005-03-06 00:56:59 I
1 b AAACJEAAFAAAAD4AAB 2005-03-06 00:56:59 I
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
3 c AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 D
已選擇7行。
Oracle根據資料字典中的資訊可以知道表T上建立了三個物化視圖,因此,MV_T_ID重新整理完之後,不會刪除物化視圖記錄。
Oracle的資料字典中還儲存著每個物化視圖上次重新整理的時間和當前的重新整理狀態。
SQL> select name, last_refresh from user_mview_refresh_times;
NAME LAST_REFRESH
------------------------------ -------------------
MV_T_ID 2005-03-06 00:56:59
MV_T_ID_NAME 2005-03-06 00:46:09
MV_T_NAME 2005-03-06 00:46:04
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
MVIEW_NAME LAST_REFRESH_DATE STALENESS
------------------------------ ------------------- -------------------
MV_T_ID 2005-03-06 00:56:59 FRESH
MV_T_ID_NAME 2005-03-06 00:46:09 NEEDS_COMPILE
MV_T_NAME 2005-03-06 00:46:04 NEEDS_COMPILE
這些視圖中記錄了每個物化視圖上次執行重新整理操作的時間,並且給出每個物化視圖中的資料是否是和基表同步的。由於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;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
1 a AAACJEAAFAAAAD4AAA 2005-03-06 00:56:59 I
1 b AAACJEAAFAAAAD4AAB 2005-03-06 00:56:59 I
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
3 c AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 D
4 d AAACJEAAFAAAAD4AAE 4000-01-01 00:00:00 I
已選擇8行。
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
MVIEW_NAME LAST_REFRESH_DATE STALENESS
------------------------------ ------------------- -------------------
MV_T_ID 2005-03-06 00:56:59 NEEDS_COMPILE
MV_T_ID_NAME 2005-03-06 00:46:09 NEEDS_COMPILE
MV_T_NAME 2005-03-06 00:46:04 NEEDS_COMPILE
下面重新整理物化視圖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;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
1 a AAACJEAAFAAAAD4AAA 2005-03-06 00:56:59 I
1 b AAACJEAAFAAAAD4AAB 2005-03-06 00:56:59 I
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
3 c AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 D
4 d AAACJEAAFAAAAD4AAE 2005-03-06 01:18:22 I
已選擇8行。
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
MVIEW_NAME LAST_REFRESH_DATE STALENESS
------------------------------ ------------------- -------------------
MV_T_ID 2005-03-06 00:56:59 NEEDS_COMPILE
MV_T_ID_NAME 2005-03-06 01:18:22 FRESH
MV_T_NAME 2005-03-06 00:46:04 NEEDS_COMPILE
如果這時再次重新整理物化視圖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;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
1 a AAACJEAAFAAAAD4AAA 2005-03-06 00:56:59 I
1 b AAACJEAAFAAAAD4AAB 2005-03-06 00:56:59 I
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 I
3 b AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
3 c AAACJEAAFAAAAD4AAD 2005-03-06 00:56:59 U
2 a AAACJEAAFAAAAD4AAC 2005-03-06 00:56:59 D
4 d AAACJEAAFAAAAD4AAE 2005-03-06 01:18:22 I
已選擇8行。
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
MVIEW_NAME LAST_REFRESH_DATE STALENESS
------------------------------ ------------------- -------------------
MV_T_ID 2005-03-06 01:25:30 FRESH
MV_T_ID_NAME 2005-03-06 01:18:22 FRESH
MV_T_NAME 2005-03-06 00:46:04 NEEDS_COMPILE
到目前為止,還沒有看到過物化視圖日誌的清除,其實每次進行完重新整理,物化視圖日誌都會試圖刪除沒有用的物化視圖日誌記錄。物化視圖日誌記錄的刪除條件是刪除那些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;
ID NAME M_ROW$$ SNAPTIME$$ D
---------- ---------- ------------------ ------------------- -
5 e AAACJEAAFAAAAD4AAF 2005-03-06 01:31:33 I
SQL> select mview_name, last_refresh_date, staleness from user_mviews;
MVIEW_NAME LAST_REFRESH_DATE STALENESS
------------------------------ ------------------- -------------------
MV_T_ID 2005-03-06 01:25:30 NEEDS_COMPILE
MV_T_ID_NAME 2005-03-06 01:18:22 NEEDS_COMPILE
MV_T_NAME 2005-03-06 01:31:33 FRESH
物化視圖MV_T_NAME重新整理了物化視圖中的每條記錄,更新了ID=5的記錄的SNAPTIME$$時間,並清除了其它所有物化視圖日誌記錄。
最後,簡單總結一下:
物化視圖在重新整理時,會重新整理所有SNAPTIME$$大於本物化視圖上次重新整理時間的記錄,並將所有是4000-01-01 00:00:00的記錄更新為當前重新整理時間。對於其他大於上次重新整理時間的記錄,只重新整理不更改。這樣,當重新整理執行完以後,資料字典中記錄當前物化視圖的上次重新整理時間為當前時刻,這保證了物化視圖日誌中目前所有的記錄都小於或等於重新整理時間。因此,每個物化視圖只要重新整理大於上次重新整理時間的記錄,且保證每次重新整理後,所有記錄的時間都小於等於上次重新整理時間,那麼無論有多少個物化視圖,就可以互不影響的使用同一個物化視圖日誌進行快速重新整理了。當物化視圖重新整理完之後,會清除那些SNAPTIME$$列小於所有物化視圖的上次重新整理時間的記錄,而這些記錄已經被所有的物化視圖都重新整理過了,儲存在物化視圖日誌中已經沒有意義了。