Diferença e significado das colunas CDB_ID e PDB_ID em um ambiente MULTI-TENANT

Recentemente, um colega num dos grupos de discussão que frequento estava com dúvidas sobre porque existem colunas CON_ID e PDB_ID nas views do sistema, quando e como usar cada uma delas. O objetivo deste artigo é esclarecer os conceitos envolvidos e tentar indicar quando e como / para que as  usar.

Quando temos um ambiente multi-tenant, nós temos um database CDB (que seria o ‘database de controle’, database “MASTER” se vc quiser fazer um paralelo com SQL SERVER, ie, um database em princípio dedicado a gerenciar os outros databases). E temos N PDBs (PDBs= Pluggable Databases, são os databases a conter os dados dos usuários finais, databases esses gerenciados/controlados pelo database CDB/MASTER, e que inclusive tem esse nome de PLUGGABLE porque podem ser desplugados de um CDB e plugados em outro)….

Então, a PRIMEIRA coisa que você TEM que saber é que pra poder obter detalhes sobre PDBs em princípio você ** TEM ** que estar conectado no banco CDB : é ELE que conhece, é ELE quem ‘controla’ os PDBs todos…. Veja só:

Estou conectado no database CDB:

 SYSTEM@xe>select cdb, name from v$database;
 
 CDB NAME
 --- ----
 YES XE
 
 1 linha selecionada.
 
 SYSTEM@xe> 

Consigo ver info sobre TODOS os PDBs controlados/gerenciados pelo CDB :

 SYSTEM@xe>select PDB_ID, PDB_NAME FROM CDB_PDBS ORDER BY 1;
 
     PDB_ID PDB_NAME
 ---------- -----------
          2 PDB$SEED
          3 XEPDB1
 
 2 linhas selecionadas.
 
 SYSTEM@xe> 

(Esse PDB$SEED é um PDB “interno”, usado como um ‘modelo’, um ‘template’ quando você vai criar um database PDB, desconsidere-o de momento)…

Muito bem. Já se estiver conectado num PDB, ** óbvio ** que ele não tem “permissão” pra enxergar os outros PDBs :

 SYSTEM@xepdb1>select PDB_ID, PDB_NAME FROM CDB_PDBS;
 
     PDB_ID PDB_NAME
 ---------- --------
          3 XEPDB1
 
 1 linha selecionada.
 
 SYSTEM@xepdb1> 

Esse é o primeiro ponto que eu TENHO que indicar, se você for administrar um ambiente MULTI-TENANT tenha CERTEZA que teus scripts gerais que desejam consultar dados gerais estão rodando no banco CDB , okdoc ??

Muito bem, agora vamos falar de CONTAINERS. Pense no CONTAINER como uma área Lógica, aonde são mantidos dados sobre os databases – e o próprio database CDB , sendo um database, tem um Container a que se refere.E a cada vez que um PDB é criado, um Container relacionado com ele é criado também:

 SYSTEM@xe>select con_id, name from v$containers order by 1;
 
     CON_ID NAME       
 ---------- -----------
          1 CDB$ROOT   
          2 PDB$SEED   
          3 XEPDB1     
 
 3 linhas selecionadas.
 
 SYSTEM@xe>

Vamos checar os PDBs controlados por este banco CDB:

 SYSTEM@xe>select PDB_ID, PDB_NAME, CON_ID FROM CDB_PDBS order by 1;
 
     PDB_ID PDB_NAME        CON_ID
 ---------- ----------- ----------
          2 PDB$SEED             2
          3 XEPDB1               3
 
 2 linhas selecionadas.
 
 SYSTEM@xe> 

Se eu criar um novo PDB, automagicamente um novo CONTAINER será registrado:

 SYSTEM@xe>create pluggable database "BOOKSTORE"
   2    admin user "BOOKSTOREADMIN"
   3    identified by "BOOKSTOREADMIN"
   4    file_name_convert = ('d:\app\oracle\product\XE\18.0.0\oradata\XE\pdbseed\', 'd:\app\oracle\product\XE\18.0.0\oradata\XE\bookstore\');
 
 Banco de dados plugável criado.
 
 SYSTEM@xe>select PDB_ID, PDB_NAME FROM CDB_PDBS;
 
 SYSTEM@xe>select con_id, name from v$containers order by 1;
 
     CON_ID NAME       
 ---------- -----------
          1 CDB$ROOT   
          2 PDB$SEED   
          3 XEPDB1     
          4 BOOKSTORE  
 
 4 linhas selecionadas. 

E taí o novo PDB também registrado:

 SYSTEM@xe>select PDB_ID, PDB_NAME, CON_ID FROM CDB_PDBS order by 1;
 
     PDB_ID PDB_NAME        CON_ID
 ---------- ----------- ----------
          2 PDB$SEED             2
          3 XEPDB1               3
          4 BOOKSTORE            4
 
 3 linhas selecionadas.
 
 SYSTEM@xe>

Vou aproveitar e já ABRIR o novo PDB também:

 SYS@xe>alter pluggable database "BOOKSTORE" open read write;
 
 Banco de dados plugável alterado.
 
 SYS@xe> 

Muito bem. Voltando à lista de PDBs e CONTAINERS acima, veja que o CONTAINER 1 (o CDB$ROOT) não aparece na view que lista os PDBs porque NÂO se relaciona a um PDB mas sim ao database CDB….

Agora SIM, chegamos no ponto do artigo que é o porquê de algumas views do sistema conterem CON_ID e outras conterem PDB_ID. CON_ID vai ser preenchido para objetos que TANTO podem estar em databases CDB quanto em PDB. Por exemplo, tablespaces.

 SYSTEM@xe>desc CDB_TABLESPACES
  Nome                                Nulo?    Tipo
  ----------------------------------- -------- -----------------
  TABLESPACE_NAME                     NOT NULL VARCHAR2(30)
  BLOCK_SIZE                          NOT NULL NUMBER
  INITIAL_EXTENT                               NUMBER
  NEXT_EXTENT                                  NUMBER
  MIN_EXTENTS                         NOT NULL NUMBER
  MAX_EXTENTS                                  NUMBER
  MAX_SIZE                                     NUMBER
  PCT_INCREASE                                 NUMBER
  MIN_EXTLEN                                   NUMBER
  STATUS                                       VARCHAR2(9)
  CONTENTS                                     VARCHAR2(21)
  LOGGING                                      VARCHAR2(9)
  FORCE_LOGGING                                VARCHAR2(3)
  EXTENT_MANAGEMENT                            VARCHAR2(10)
  ALLOCATION_TYPE                              VARCHAR2(9)
  PLUGGED_IN                                   VARCHAR2(3)
  SEGMENT_SPACE_MANAGEMENT                     VARCHAR2(6)
  DEF_TAB_COMPRESSION                          VARCHAR2(8)
  RETENTION                                    VARCHAR2(11)
  BIGFILE                                      VARCHAR2(3)
  PREDICATE_EVALUATION                         VARCHAR2(7)
  ENCRYPTED                                    VARCHAR2(3)
  COMPRESS_FOR                                 VARCHAR2(30)
  DEF_INMEMORY                                 VARCHAR2(8)
  DEF_INMEMORY_PRIORITY                        VARCHAR2(8)
  DEF_INMEMORY_DISTRIBUTE                      VARCHAR2(15)
  DEF_INMEMORY_COMPRESSION                     VARCHAR2(17)
  DEF_INMEMORY_DUPLICATE                       VARCHAR2(13)
  SHARED                                       VARCHAR2(13)
  DEF_INDEX_COMPRESSION                        VARCHAR2(8)
  INDEX_COMPRESS_FOR                           VARCHAR2(13)
  DEF_CELLMEMORY                               VARCHAR2(14)
  DEF_INMEMORY_SERVICE                         VARCHAR2(12)
  DEF_INMEMORY_SERVICE_NAME                    VARCHAR2(1000)
  LOST_WRITE_PROTECT                           VARCHAR2(7)
  CHUNK_TABLESPACE                             VARCHAR2(1)
  CON_ID                                       NUMBER
 
 SYSTEM@xe> 

Em views que se referem à PDBs, vou ter o PDB_ID:

 SYSTEM@xe>desc cdb_pdbs
 Nome                                      Nulo?    Tipo
 ----------------------------------------- -------- --------------
 PDB_ID                                    NOT NULL NUMBER
 PDB_NAME                                  NOT NULL VARCHAR2(128)
 DBID                                      NOT NULL NUMBER
 CON_UID                                   NOT NULL NUMBER
 GUID                                               RAW(16)
 STATUS                                             VARCHAR2(10)
 CREATION_SCN                                       NUMBER
 VSN                                                NUMBER
 LOGGING                                            VARCHAR2(9)
 FORCE_LOGGING                                      VARCHAR2(39)
 FORCE_NOLOGGING                                    VARCHAR2(3)
 APPLICATION_ROOT                                   VARCHAR2(3)
 APPLICATION_PDB                                    VARCHAR2(3)
 APPLICATION_SEED                                   VARCHAR2(3)
 APPLICATION_ROOT_CON_ID                            NUMBER
 IS_PROXY_PDB                                       VARCHAR2(3)
 CON_ID                                    NOT NULL NUMBER
 UPGRADE_PRIORITY                                   NUMBER
 APPLICATION_CLONE                                  VARCHAR2(3)
 FOREIGN_CDB_DBID                                   NUMBER
 UNPLUG_SCN                                         NUMBER
 FOREIGN_PDB_ID                                     NUMBER
 CREATION_TIME                             NOT NULL DATE
 REFRESH_MODE                                       VARCHAR2(6)
 REFRESH_INTERVAL                                   NUMBER
 TEMPLATE                                           VARCHAR2(3)
 LAST_REFRESH_SCN                                   NUMBER
 TENANT_ID                                          VARCHAR2(255)
 SNAPSHOT_MODE                                      VARCHAR2(6)
 SNAPSHOT_INTERVAL                                  NUMBER

SYSTEM@xe>

Já que não tenho PDB_ID, tenho que usar CON_ID para consultar as tablespaces na view:

 SYSTEM@xe>select distinct CON_ID from CDB_TABLESPACES;

    CON_ID
----------
         1
         3
         4

3 linhas selecionadas.

SYSTEM@xe>

Novamente, o Container 1 se refere a um database que não É PDB, então esse CON_ID = 1 não estará contido nas views que se referem à PDBs, como por exemplo a CDB_PDBS. Assim sendo, tenho que fazer um OUTER JOIN se eu quiser mostrar as tablespaces TODAS existentes no ambiente e (quando for uma tablespace referente à PDB) mostrar o nome do PDB:

 SYSTEM@xe>select A.CON_ID, B.PDB_NAME, A.TABLESPACE_NAME from CDB_TABLESPACES A, CDB_PDBS B
   2  WHERE A.CON_ID = B.PDB_ID (+)
   3  order by 1,3;
 
     CON_ID PDB_NAME    TABLESPACE_NAME
 ---------- ----------- ------------------------------
          1             SYSAUX
          1             SYSTEM
          1             TEMP
          1             UNDOTBS1
          1             USERS
          3 XEPDB1      SYSAUX
          3 XEPDB1      SYSTEM
          3 XEPDB1      TEMP
          3 XEPDB1      UNDOTBS1
          3 XEPDB1      USERS
          4 BOOKSTORE   SYSAUX
          4 BOOKSTORE   SYSTEM
          4 BOOKSTORE   TEMP
          4 BOOKSTORE   UNDOTBS1
 
 14 linhas selecionadas.
 
 SYSTEM@xe> 

Ou seja, a resposta final do artigo é: Nem todas as views do sistema ORACLE num banco CDB possuem PDB_ID porque nem todas se relacionam à PDBs, E caso haja a necessidade de joinear uma view com info de PDB contra uma sem (como foi o caso acima) pode se usar CON_ID…

Para finalizar, três observações:

1. Antes de mais nada, eu tenho que lembrar que a opção de MULTI-TENANT (ie, ter múltiplos databases ‘controlados’ por um database ‘mestre’ e com um só conjunto de processos principais atendendo a todos) foi introduzida na versão 12.1 do RDBMS Oracle, na forma mais simples,que foi a referenciada aqui no artigo. Na versão 12.2 em diante foi INTRODUZIDA a funcionalidade de Application Containers, que é um elemento lógico de controle ADICIONAL a mais para levar em conta.

Os interessados podem se aprofundar lendo este link e a série de artigos sobre Oracle Multi-tenant.

2. A numeração constante na coluna CON_ID ** não é ** aleatória/desprovida de significado. A documentação da Oracle já nos diz:

Each container data object contains a CON_ID column that identifies the container for each row returned.

E também:

Table 19-1 describes the meanings of the values in the CON_ID column.

Table 19-1 CON_ID Column in Container Data Objects

Value in CON_ID Column Description

0                       The data pertains to the entire CDB

1                       The data pertains to the CDB root

2                       The data pertains to the PDB seed

3 – 4,098         The data pertains to a PDB, an application root, or an application PDB

Ou seja, CON_ID é o identificador DO CONTAINER ao qual o PDB está relacionado, os Containers 0, 1 e 2 são ‘reservados’, de uso interno, possuem significado diferenciado nas views do sistema…

3. Não está relacionado com o tema do Artigo, mas há o conceito de CURRENT CONTAINER – mudando o CONTAINER ao qual você está conectado, você passa a enxergar os dados como se estivesse conectado ao database relacionado com o novo CONTAINER:

 SYSTEM@xe>alter session set CONTAINER=XEPDB1;

Sessão alterada.

Pronto. Passo a enxergar os dados como se tivesse conectado ao database XEPDB1, que se relaciona com o container XEPDB1:

 SYSTEM@xe>select con_id, name from v$containers;

    CON_ID NAME
---------- -----------
         3 XEPDB1

1 linha selecionada.

SYSTEM@xe>select PDB_ID, PDB_NAME FROM CDB_PDBS ORDER BY 1;

    PDB_ID PDB_NAME
---------- -----------
         3 XEPDB1

1 linha selecionada.

SYSTEM@xe>

=> Obviamente, só pode fazer isso um usuário Autorizado/permissionado…

Abraços