Oracle資料庫中物化檢視的原理剖析

47328983發表於2011-05-11

物化檢視 (MV)在一個段中儲存查詢結果,並且能夠在提交查詢時將結果返回給使用者,從而不再需要重新執行查詢 — 在查詢要執行幾次時(這在資料倉儲環境中非常常見),這是一個很大的好處。物化檢視可以利用一個快速重新整理機制從基礎表中全部或增量重新整理。

假定您已經定義了一個物化檢視,如下:

  create materialized view mv_hotel_resv
  refresh fast
  enable query rewrite
  as
  select distinct city, resv_id, cust_name
  from hotels h, reservations r 
  where r.hotel_id = h.hotel_id';
  

您如何才能知道已經為這個物化檢視建立了其正常工作所必需的所有物件?在 Oracle 資料庫 10g 之前,這是用 DBMS_MVIEW 程式包中的 EXPLAIN_MVIEW 和 EXPLAIN_REWRITE 過程來判斷的。這些過程(在 10g 中仍然提供)非常簡要地說明一種特定的功能 — 如快速重新整理功能或查詢重寫功能 — 可能用於上述的物化檢視,但不提供如何實現這些功能的建議。相反,需要對每一個物化檢視的結構進行目視檢查,這是非常不實際的。

在 10g 中,新的 DBMS_ADVISOR 程式包中的一個名為 TUNE_MVIEW 的過程使得這項工作變得非常容易:您利用 IN 引數來呼叫程式包,這構造了物化檢視建立指令碼的全部內容。該過程建立一個顧問程式任務 (Advisor Task),它擁有一個特定的名稱,僅利用 OUT 引數就能夠把這個名稱傳回給您。

下面是一個例子。因為第一個引數是一個 OUT 引數,所以您需要在 SQL*Plus 中定義一個變數來儲存它。

  
  SQL> -- 首先定義一個變數來儲存 OUT 引數
  SQL> var adv_name varchar2(20)
  SQL> begin
  2 dbms_advisor.tune_mview
   3   (
  4    :adv_name,
  5    'create materialized view mv_hotel_resv 
    refresh fast enable query rewrite as
  select distinct city, resv_id, cust_name from hotels h, 
     reservations r where r.hotel_id = h.hotel_id');
  6* end;
 
 

現在您可以在該變數中找出顧問程式的名稱。

  
  SQL> print adv_name
  
  ADV_NAME
  -----------------------
  TASK_117
  

接下來,通過查詢一個新的 DBA_TUNE_MVIEW 來獲取由這個顧問程式提供的建議。務必在執行該命令之前執行 SET LONG 999999,因為該檢視中的列語句是一個 CLOB,預設情況下只顯示 80 個字元。

  
  select script_type, statement 
  from  dba_tune_mview 
  where task_name = 'TASK_117' 
  order by script_type, action_id;
  

下面是輸出:

  
  SCRIPT_TYPE  STATEMENT
  -------------- -----------------------------------------------------------
  IMPLEMENTATION CREATE MATERIALIZED VIEW LOG ON "ARUP"."HOTELS" WITH ROWID,
  SEQUENCE ("HOTEL_ID","CITY") INCLUDING NEW VALUES
  
  IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE ON "ARUP"."HOTELS" ADD
  ROWID, SEQUENCE ("HOTEL_ID","CITY") INCLUDING NEW VALUES
  
  IMPLEMENTATION CREATE MATERIALIZED VIEW LOG ON "ARUP"."RESERVATIONS" WITH
  ROWID, SEQUENCE ("RESV_ID","HOTEL_ID","CUST_NAME")
  INCLUDING NEW VALUES
  
  IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE ON "ARUP"."RESERVATIONS"
  ADD ROWID, SEQUENCE ("RESV_ID","HOTEL_ID","CUST_NAME")
  INCLUDING NEW VALUES
  
  IMPLEMENTATION CREATE MATERIALIZED VIEW ARUP.MV_HOTEL_RESV  REFRESH FAST
  WITH ROWID ENABLE QUERY REWRITE AS SELECT
  ARUP.RESERVATIONS.CUST_NAME C1, ARUP.RESERVATIONS.RESV_ID
  C2, ARUP.HOTELS.CITY C3, COUNT(*) M1 FROM ARUP.RESERVATIONS,
  ARUP.HOTELS WHERE ARUP.HOTELS.HOTEL_ID =
  ARUP.RESERVATIONS.HOTEL_ID GROUP BY
  ARUP.RESERVATIONS.CUST_NAME, ARUP.RESERVATIONS.RESV_ID,
  ARUP.HOTELS.CITY
  
  UNDO      DROP MATERIALIZED VIEW ARUP.MV_HOTEL_RESV
  

SCRIPT_TYPE 列顯示建議的性質。大多數行將要執行,因此名稱為 IMPLEMENTATION。如果接受,則需按照由 ACTION_ID 列指出的特定順序執行建議的操作。

如果您仔細檢視這些自動生成的建議,那麼您將注意到它們與您自己通過目視分析生成的建議是類似的。這些建議合乎邏輯;快速重新整理的存在需要在擁有適當子句(如那些包含新值的子句)的基礎表上有一個 MATERIALIZED VIEW LOG。STATEMENT 列甚至提供了實施這些建議的確切 SQL 語句。

在實施的最後一個步驟中,顧問程式建議改變建立物化檢視的方式。注意我們的例子中的不同之處:將一個 count(*) 新增到了物化檢視中。因為我們將這個物化檢視定義為可快速重新整理的,所以必須有 count(*),以便顧問程式糾正遺漏。

TUNE_MVIEW 過程不僅在建議方面超越了在 EXPLAIN_MVIEW 和 EXPLAIN_REWRITE 中提供的功能,還為建立相同的物化檢視指出了更容易和更高效的途徑。有時,顧問程式可以實際推薦多個物化檢視,以使查詢更加高效。

您可能會問,如果任何一個經驗豐富的 DBA 都能夠找出 MV 建立指令碼中缺了什麼,然後自己糾正它,那這還有什麼用?嗯,顧問程式正是用來完成這項工作的:它是一位經驗豐富、高度自覺的自動資料庫管理員,它可以生成能與人的建議相媲美的建議,但有一個非常重要的不同之處:它免費工作,並且不會要求休假或加薪。這一好處使高階 DBA 解放出來,將日常的工作交給較低階的 DBA,從而允許他們將其專業技能應用到更具有戰略意義的目標上。

您還可以將顧問程式的名稱作為值傳遞給 TUNE_MVIEW 過程中的引數,這將使用該名稱而非系統生成的名稱生成一個的顧問程式。

---------------------------
 

實體化檢視概述

    Oracle的實體化檢視提供了強大的功能,可以用在不同的環境中。在不同的環境中,實體化檢視的作用也不相同。資料倉儲中的實體化檢視主要用於預先計算並儲存表連線或聚集等耗時較多的操作的結果,這樣,在執行查詢時,就可以避免進行這些耗時的操作,而從快速的得到結果。在資料倉儲中,還經常使用查詢重寫(query rewrite)機制,這樣不需要修改原有的查詢語句,Oracle會自動選擇合適的實體化檢視進行查詢,完全對應用透明。實體化檢視和表一樣可以直接進行查詢。實體化檢視可以基於分割槽表,實體化檢視本身也可以分割槽。除了在資料倉儲中使用,實體化檢視還用於複製、移動計算等方面。實體化檢視有很多方面和索引很相似:使用實體化檢視的目的是為了提高查詢效能;實體化檢視對應用透明,增加和刪除實體化檢視不會影響應用程式中SQL語句的正確性和有效性;實體化檢視需要佔用儲存空間;當基表發生變化時,實體化檢視也應當重新整理。

 
 

物化檢視日誌的維護

    物化檢視日誌經常會由於物化檢視長時間沒有重新整理,或者基表的一次批量資料更改而變得很大,這會影響物化檢視的重新整理效能,因此對於這種情況需要對物化檢視日誌進行處理,降低物化檢視日誌表的高水位線。

  

Oracle的物化檢視的快速重新整理功能,主要是靠物化檢視日誌來實現的。

物化檢視日誌會記錄下基表所有的增、刪、改操作,而物化檢視執行完快速重新整理操作後,會從物化檢視日誌中將本物化檢視重新整理過且其他物化檢視所不需要重新整理的記錄刪除掉。如果其中一個物化檢視一直不重新整理,那麼物化檢視日誌就會變得越來越大。

還有一種情況,比如表中插入了大量的資料,或者刪除了大量的資料,或者將表中的某一列統一更新為一個值,這種操作都會在物化檢視日誌中產生大量的記錄。

而物化檢視日誌的增大必然影響物化檢視的重新整理速度。一方面,物化檢視在重新整理的時候要掃描物化檢視日誌,另一方面,物化檢視在重新整理介紹後,也要清除物化檢視日誌中的記錄,仍然要掃描物化檢視日誌,因此物化檢視日誌的大小直接會影響物化檢視快速重新整理的速度。更重要的是,物化檢視日誌的高水位一旦增長到一個很高的位置,即使以後物化檢視日誌中記錄很少,甚至沒有記錄存在,物化檢視在重新整理的時候仍然需要較長的時間。

因此,在對於物化檢視的基表進行操作時,應注意儘量更新需要更新的記錄:

SQL> CREATE TABLE T (ID NUMBER PRIMARY KEY, NAME VARCHAR2(30));

表已建立。

SQL> INSERT INTO T SELECT ROWNUM, OBJECT_NAME FROM DBA_OBJECTS;

已建立50674行。

SQL> CREATE MATERIALIZED VIEW LOG ON T;

實體化檢視日誌已建立。

用一個最簡單的例子來說明什麼叫做更新需要更新的記錄。現在需要將T表中的NAME欄位全部用大寫來表示,最簡單的寫法:

SQL> UPDATE T SET NAME = UPPER(NAME);

已更新50674行。

SQL> SELECT COUNT(*) FROM MLOG$_T;

  COUNT(*)
----------
     50674

SQL> ROLLBACK;

回退已完成。

但是這種寫法就會造成一些沒有必要更新的記錄也執行了更新操作,從而導致物化檢視日誌中記錄了很多沒有必要重新整理的記錄,這些記錄不但影響物化檢視日誌的高水位線,而且會增加物化檢視重新整理的成本。

對於物化檢視的基表,這個重新整理則應該改寫為:

SQL> UPDATE T SET NAME = UPPER(NAME) WHERE NAME != UPPER(NAME);

已更新34007行。

SQL> SELECT COUNT(*) FROM MLOG$_T;

  COUNT(*)
----------
     34007

採用這種方式就可以避免重新整理不必要的列而使得物化檢視日誌變得很大。

不過有的時候大資料量的操作無可避免,或者物化檢視日誌本身已經變得很大,已經開始影響物化檢視的重新整理效能了,那麼就只能通過維護物化檢視日誌表的方式來降低高水位線。

不應該對物化檢視日誌執行TRUNCATE TABLE操作。因為即使查詢物化檢視日誌表中不存在記錄,也無法確保在執行TRUNCATE TABLE操作之前,沒有其他會話修改物化檢視基表,從而導致新的記錄插入物化檢視日誌中。

一旦發生物化檢視日誌記錄被TRUNCATE的情況,就會導致物化檢視和物化檢視基表的資料不一致。例如:

SQL> CREATE MATERIALIZED VIEW MV_T REFRESH FAST AS SELECT * FROM T;

實體化檢視已建立。

SQL> INSERT INTO T VALUES (60000, 'A');

已建立1行。

SQL> TRUNCATE TABLE MLOG$_T;

表被截斷。

SQL> INSERT INTO T VALUES (60001, 'B');

已建立1行。

SQL> EXEC DBMS_MVIEW.REFRESH('MV_T')

PL/SQL過程已成功完成。

SQL> SELECT * FROM MV_T WHERE ID >= 60000;

        ID NAME
---------- ------------------------------

     60001 B

即使採用LOCK表的方式配合TRUNCATE,也無法避免併發的問題。

嘗試在TRUNCATE之前LOCK物化檢視日誌表,很可能在TRUNCATE操作的時候失敗:

SQL> LOCK TABLE MLOG$_T IN EXCLUSIVE MODE;

表已鎖定。

會話1鎖定物化檢視日誌表,這時會話2插入基表一條記錄:

SQL> SET SQLP 'SQL2> '
SQL2> INSERT INTO T VALUES (60002, 'C');

會話1執行TRUNCATE語句:

SQL> TRUNCATE TABLE MLOG$_T;
              
1行出現錯誤:
ORA-00054:
資源正忙,但指定以NOWAIT方式獲取資源

會話2成功插入記錄:

已建立1行。

SQL2> SELECT ID FROM MLOG$_T;

       
----------
    60002

這是由於會話1執行TRUNCATE操作,會先發出一個COMMIT,從而釋放了MLOG$_T上的鎖,而這時會話2獲得了MLOG$_T上的鎖,並插入記錄。由於會話2獲得了物化檢視日誌上的鎖,會話1嘗試TRUNCATE就會失敗。

如果嘗試在基表上加鎖,雖然可以避免基表的修改造成的物化檢視日誌改變,但是無法避免手工修改物化檢視日誌表的情況,雖然這種情況基本上不會發生。

因此處理物化檢視高水位線最穩妥的方法還是使用MOVE的方式。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/241379/viewspace-694981/,如需轉載,請註明出處,否則將追究法律責任。

相關文章