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

时间:2022-05-05
本文章向大家介绍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这个脚本里的内容吧,这里有你想了解的一切。