Oracle 12c 多租戶專題|CDB後設資料內幕

shilei1發表於2018-05-13
沃趣科技·周天鵬

原文連結 https://blog.dbi-services.com/oracle-12c-cdb-metadata-a-object-links-internals/


溫馨提示:這篇文章只適合那些想了解多租戶環境下資料字典、後設資料和物件連結相關技術內幕的geek群體!對於你日常運維資料庫來說並沒有什麼太大用處。千萬別再生產環境上這麼搞,你可能會損毀你的資料字典。

在12c的CDB中,我們知道每個PDB都是獨立的。但這些PDB為了能整合到一個CDB裡,會共享一些公共資源。例如,CPU、記憶體、redo和undo。他們都被例項在CDB級別進行管理。對於資料來說,共享公共資源也很簡單,因為PDB有自己獨立的表空間,而且,可插拔特性僅僅是可傳輸表空間技術的一種擴充。

對於12c的多租戶架構來說,最具挑戰性的技術難題是如何共享資料字典。

首先,雖然每個PDB有自己的後設資料描述自己獨有的資訊。但是,資料字典自身的後設資料必須共享,舉個例子就是,所有dbms_xxx的PL/SQL包都儲存在CDB$ROOT中,PDB中僅存放指向他們的一個連結。

除此之外,一些資料字典中的資料也必須被共享,例如一些引用表(AUDIT_ACTIONS)或者公共資料庫(利用AWR資料構造出的DBA_HIST_xxx這種表),他們也都儲存在CDB$ROOT中,每個PDB僅定義一個檢視指向他們。

最後,CDB$ROOT必須有能力查詢所有PDB的資料。例如透過12c新增的CDB_xxx檢視。雖然他們暴露為用來查詢容器資料的物件,但其實他們真正查詢的資料還是儲存在每個PDB中。

這聽起來似乎有點迷,雖然官方文件也不會很深入的講具體的實現原理。但幸運的是?/rdbms/admin這個指令碼中有一些我們想要的線索。這裡描述了當 "_ORACLE_SCRIPT"引數置為true時,SQL語法將如何進行擴充。

所以,geek們來了,讓我們一起嘗試下自己建立後設資料和物件連結。

下面的操作需要先把我們當前會話的"_ORACLE_SCRIPT"引數置為true。

然後,我們將看到一種新的擴充SQL語法:cdb$view(), sharing=metadata, sharing=object, common_data

容器資料物件

首先讓我們看下根容器如何檢視其他容器的資料。

我在根容器中:


SQL> alter session set container=cdb$root;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 CDB$ROOT
SQL> show con_id
 CON_ID
 ------------------------------
 1


建立一個規則表:

SQL> create table DEMO_REG_TABLE sharing=none as select 111 dummy from dual;
 Table created.
SQL> select * from DEMO_REG_TABLE;
      DUMMY
 ----------
        111


然後,我在PDB中執行相同操作(但資料不同):

SQL> alter session set container=pdb1;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 PDB1
SQL> show con_id
 CON_ID
 ------------------------------
 3
SQL> create table DEMO_REG_TABLE sharing=none as select 999 dummy from dual;
 Table created.
SQL> select * from DEMO_REG_TABLE;
      DUMMY
 ----------
        999


這時,回到根容器,我使用CDB$VIEW函式來檢視所有PDB中的資訊。

SQL> select * from  cdb$view(DEMO_REG_TABLE) where con_id in (1,3);
      DUMMY     CON_ID
 ---------- ----------
        999          3
        111          1


這就是內建容器物件的定義方式。他們用CDB$VIEW來查詢每個PDB中的資料。整合後的結果加上CON_ID來表示這些資料來自哪個PDB。

想知道具體如何實現嗎?目測是用了一個執行在每個PDB上的並行查詢。證據如下:
先前我的查詢條件是CON_ID in (1,3),因為我沒有在所有PDB上建立我的表。當我不加這個where條件時,我會收到如下報錯:


SQL> select * from  cdb$view(DEMO_REG_TABLE);
 select * from  cdb$view(DEMO_REG_TABLE)
 *
 ERROR at line 1:
 ORA-12801: error signaled in parallel query server P002
 ORA-00942: table or view does not exist


並行程式的報錯,這個PDB裡找不到表了。

後設資料連結

現在我將在根容器和PDB中建立一個函式。但是我不想讓這些程式碼被儲存兩份。我會使用SHARING=METADATA來定義後設資料連結。


SQL> alter session set container=cdb$root;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 CDB$ROOT
SQL> show con_id
CON_ID
 ------------------------------
 1
SQL> create function DEMO_MDL_FUNCTION sharing=metadata
   2  return varchar2 as dummy varchar2(100); begin select max(dummy) into dummy from DEMO_REG_TABLE; return dummy; end;
   3  /
 Function created.
SQL> select DEMO_MDL_FUNCTION from dual;
 DEMO_MDL_FUNCTION
 ------------------------------
 111


這是我CDB$ROOT中的函式,它展示我的CDB$ROOT中的的一張普通表裡的內容。
現在,在PDB中做同樣的操作。


SQL> alter session set container=pdb1;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 PDB1
SQL> show con_id
 CON_ID
 ------------------------------
 3
SQL> create function DEMO_MDL_FUNCTION sharing=metadata
   2  return varchar2 as dummy varchar2(100); begin select max(dummy) into dummy from DEMO_REG_TABLE; return dummy; end;
   3  /
 Function created.
SQL> select DEMO_MDL_FUNCTION from dual;
 DEMO_MDL_FUNCTION
 ------------------------------
 999


我在我的PDB中有了一個同樣的函式,展現PDB中的一張普通表內的資料。

我可以從SYS.SOURCE$資料字典中查出我定義的函式的後設資料。如下:


SQL> alter session set container=cdb$root;
 Session altered.
SQL> select * from source$ where obj# in (select obj# from obj$ where name like 'DEMO%');
      OBJ#       LINE SOURCE
 ---------- ---------- ------------------------------
      95789          1 function DEMO_MDL_FUNCTION


但是,再看下我們的PDB中有啥:


SQL> alter session set container=pdb1;
 Session altered.
SQL> select * from source$ where obj# in (select obj# from obj$ where name like 'DEMO%');
no rows selected


結果發現PDB中啥也沒有存,只能在obj$中查到這個物件,型別是後設資料連結。

但如果我再查一下dba_source,這裡又有另一個迷:


SQL> select * from dba_source where name like 'DEMO%';
OWNER NAME              TYPE      LINE TEXT                        ORIGIN_CON_ID
 ----- ----------------- --------- ---- --------------------------- -------------
 SYS   DEMO_MDL_FUNCTION FUNCTION     1 function DEMO_MDL_FUNCTION              1


PDB的DBA_SOURCE中包含了CDB$ROOT中的資訊,元資訊欄位的後面加了ORIGIN_CON_ID這個欄位來表示該資訊來自PDB的資料字典還是CDB$ROOT的資料字典。這裡顯然表示了該函式來自CDB$ROOT。(公共資料檢視部分有詳解)

物件連結

我們已經看到了CDB$ROOT是如何儲存所有PDB的元資訊的。我們將使用後設資料連線來建立一張表。除此之外,我們還要建立一個物件連結,這樣CDB$ROOT中的表才能儲存所有PDB的資訊。我用SHARING=METADATA來建表,SHARING=OBJECT來建檢視。

首先,我在所有容器中建表:


SQL> alter session set container=cdb$root;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 CDB$ROOT
SQL> show con_id
 CON_ID
 ------------------------------
 1
SQL> create table DEMO_MDL_TABLE sharing=metadata as select 111 dummy from dual;
 Table created.
SQL> alter session set container=pdb1;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 PDB1
SQL> show con_id
CON_ID
 ------------------------------
 3
SQL> create table DEMO_MDL_TABLE sharing=metadata as select 999 dummy  from dual;
 Table created.


這樣每個容器中就都建立了這張表。為了更好的理解發生了什麼,我往這些表裡插入不同的資料。接下來用CDB$VIEW查詢所有容器中的資料。


SQL> alter session set container=cdb$root;
 Session altered.
SQL> select * from  cdb$view(DEMO_MDL_TABLE) where con_id in (1,3);
     DUMMY     CON_ID
 ---------- ----------
        999          3
        111          1


這是兩張表結構相同的表,CDB$ROOT中的資料是111,PDB中的是999。

我要在這個表上建立一個檢視,定義它是一個物件連結,這樣裡面的資料就可以被共享了。


SQL> alter session set container=cdb$root;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 CDB$ROOT
SQL> show con_id
 CON_ID
 ------------------------------
 1
SQL> create view DEMO_OBL_VIEW sharing=object as select * from DEMO_MDL_TABLE;
 View created.
SQL> select * from DEMO_OBL_VIEW;
      DUMMY
 ----------
        111


CDB$ROOT中的檢視展示了CDB$ROOT中的資料,現在我們再PDB中做同樣的操作。


SQL> alter session set container=pdb1;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 PDB1
SQL> show con_id
 CON_ID
 ------------------------------
 3
SQL> create view DEMO_OBL_VIEW sharing=object as select * from DEMO_MDL_TABLE;
 View created.
SQL> select * from DEMO_OBL_VIEW;
      DUMMY
 ----------
        111


PDB中的這個檢視也展示了CDB$ROOT中的資料。這個查詢用了物件連結,而不是使用當前容器中的表。

想想AWR快照,AWR快照只執行在CDB級別,然後將資料存在WRM$表中。最終每個PDB依然可以透過DBA_HIST_*檢視來檢視這些資料。
PS: 你無法向一個物件連結中插入資料


SQL> insert into DEMO_OBL_VIEW select 9999 dummy from dual;
 insert into DEMO_OBL_VIEW select 9999 dummy from dual
             *
 ERROR at line 1:
 ORA-02030: can only select from fixed tables/views


這裡有一個關於實現方法的線索,如果你從PDB中看執行計劃,你可以發現物件連結訪問的是一個fixed table。


---------------------------------------------
 | Id  | Operation        | Name             |
 ---------------------------------------------
 |   0 | SELECT STATEMENT |                  |
 |   1 |  FIXED TABLE FULL| X$OBLNK$aed0818c |
 ---------------------------------------------


公共資料檢視

最後讓我們看看PDB是如何展示來自CDB$ROOT中的資料的。 像DBA_SOURCE這種字典表,必須要展示公共後設資料和PDB後設資料。它被定義為公共資料檢視,我這就用COMMON_DATA關鍵字建立一個。


SQL> alter session set container=cdb$root;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 CDB$ROOT
SQL> show con_id
 CON_ID
 ------------------------------
 1
SQL> create or replace view DEMO_INT_VIEW common_data (dummy,sharing) as select dummy,case when dummy='222' then 0 else 1 end from DEMO_MDL_TABLE;
 View created.
SQL> select * from DEMO_INT_VIEW;
     DUMMY    SHARING
 ---------- ----------
        111          1
        222          0


我增加了一個“SHARING”欄位(使用COMMON_DATA關鍵字時必須要有)來標記那些行是共享給其他容器,那些行是不共享的。“222”那行是這個容器私有的,“111”那行可以被其他PDB看到。我在PDB中也要做相同的操作:


SQL> alter session set container=pdb1;
 Session altered.
SQL> show con_name
 CON_NAME
 ------------------------------
 PDB1
SQL> show con_id
 CON_ID
 ------------------------------
 3
SQL> create or replace view DEMO_INT_VIEW common_data (dummy,sharing) as select dummy,case when dummy='222' then 0 else 1 end from DEMO_MDL_TABLE;
 View created.
SQL> select * from DEMO_INT_VIEW;
      DUMMY    SHARING ORIGIN_CON_ID
 ---------- ---------- -------------
        999          1             3
        111          1             1


當再PDB中時,COMMON_DATA檢視除了PDB中的行,還會展示CDB$ROOT中共享的行。當然,從上面讀下來後,你期待著看到並行程式和fixed table:


SQL> set autotrace on
 SQL> select * from DEMO_INT_VIEW;
     DUMMY    SHARING ORIGIN_CON_ID
 ---------- ---------- -------------
        111          1             1
        999          1             3
Execution Plan
 ----------------------------------------------------------
 Plan hash value: 3158883863
--------------------------------------------------------------------------------------------
 |Id  | Operation               | Name            |Pstart|Pstop |   TQ  |IN-OUT| PQ Distrib |
 --------------------------------------------------------------------------------------------
 |  0 | SELECT STATEMENT        |                 |      |      |       |      |            |
 |  1 |  PX COORDINATOR         |                 |      |      |       |      |            |
 |  2 |   PX SEND QC (RANDOM)   | :TQ10000        |      |      | Q1,00 | P->S | QC (RAND)  |
 |  3 |    PX PARTITION LIST ALL|                 |    1 |    2 | Q1,00 | PCWC |            |
 |  4 |     FIXED TABLE FULL    | X$COMVW$e40eb386|      |      | Q1,00 | PCWP |            |
 --------------------------------------------------------------------------------------------


這個fixed table把每個容器中的資料作為一個分割槽返回,均為並行處理。

對於多租戶環境下資料字典的技術內幕,我們的探索已經足夠了。
如果你還想知道更多,就看看?/rdbms/admin/noncdb_to_pdb.sql這個指令碼里的內容吧,這裡有你想了解的一切。


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

相關文章