Oracle 12c 多租户专题|CDB元数据内幕

沃趣科技·周天鹏

原文链接 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/28218939/viewspace-2146885/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/28218939/viewspace-2146885/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值