Multitenant, Monitoring von CDB und PDB

02.
April
2024
Veröffentlicht von: Holger Eekhoff

Um in der Multitenant Umgebung - sprich bei CDB (Container Database) / PDB (Pluggable Database) - nicht alles doppelt und dreifach zu überwachen sind gerade im RAC Umfeld ein paar kleine Kniffe zu beachten. Für das Monitoring kann es in den meisten Fällen ausreichen mit einem Connect auf die CDB auch die Informationen aller PDBs mit abzufragen. So lassen sich mit einer Abfrage die Informationen z. B. aller Tablespaces oder alle invalide Objekte der CDB sowie der dort laufenden PDBs anzeigen

Vorbereitungen

Zuerst werden in der CDB, der PDB$SEED und den weiteren PDBs je ein Tablespace „TEST“ mit unterschiedlicher Größe erstellt, um die Unterschiede bei den unterschiedlichen Abfragen zu verdeutlichen.

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDBEINS                        READ WRITE NO
         4 PDBZWEI                        READ WRITE NO


SQL> -- Anlage von TEST Tablespaces in den PDBs und der CDB

SQL> alter session set container=PDBEINS;
SQL> create tablespace  TEST datafile size 10M;
SQL> alter session set container=PDBZWEI;
SQL> create tablespace  TEST datafile size 20M;
SQL> alter session set container=CDB$ROOT;
SQL> create tablespace  TEST datafile size 1M;

SQL> -- Die PDB$SEED wird schreibend geoeffnet und auch dort eine TEST Tablespace erstellt 
 
SQL> alter pluggable database PDB$SEED close instances=ALL;
SQL> alter pluggable database PDB$SEED open instances=ALL;
SQL> alter session set container=PDB$SEED;
SQL> create tablespace  TEST datafile size 2M;

SQL> -- Abschliessend oeffnen der PDB$SEED im READ ONLY Mode

SQL> alter session set container=CDB$ROOT;
SQL> alter pluggable database PDB$SEED close instances=ALL;
SQL> alter pluggable database PDB$SEED open read only instances=ALL;

 

CDB_* Views

Mit Einführung der Multitenant Umgebung hat Oracle dem Administrator neben den ALL_*, USER_* und  den DBA_* neu die CDB_* Views spendiert. Die CDB_* Views sind ein Abbild der DBA_* Views, erweitert um eine Spalte CON_ID, die die ID des Containers ausgibt.
In einer herkömmlichen (non-CDB) Datenbank existieren diese Views ebenfalls, obwohl es keine Container gibt. Dies erkennt man an der CON_ID=0.

Die CDB (Containername CDB$ROOT)  hat immer die CON_ID=1
Der Container PDB$SEED bekommt immer die CON_ID=2
Alle anderen selbst erstellten PDBs erhalten eine fortlaufende ID > 2.
In den meisten Fällen werden in den CDB_* Views keine Informationen zur PDB$SEED (CON_ID=2) ausgegeben.

Ist man auf einer PDB angemeldet so werden nur Daten zu der PDB ausgegeben, auf der CDB jedoch werden die Daten zu der CDB und aller PDBs präsentiert:

SQL> alter session set container=PDBEINS

SQL> -- Abfrage auf DBA_* View in einer PD

SQL> select TABLESPACE_NAME, bytes/1024/1024 MB 
     from dba_data_files where TABLESPACE_NAME ='TEST'

TABLESPACE_NAME                        MB
------------------------------ ----------
TEST                                   1

SQL> -- Abfrage auf CDB_* View in einer PDB (Anzeige nur des TBS der akt. PDB

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB 
     from cdb_data_files where TABLESPACE_NAME ='TEST' order by con_id

    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         3 TEST                                   1

SQL> -- Abfrage auf DBA_* View in der CD

SQL> alter session set container=CDB$ROOT;
SQL> select TABLESPACE_NAME, bytes/1024/1024 MB 
     from dba_data_files where TABLESPACE_NAME ='TEST'

TABLESPACE_NAME                        MB
------------------------------ ----------
TEST                                    

SQL> -- Wechsel in die CDB

SQL> alter session set container=CDB$ROOT;

SQL> -- Abfrage auf CDB_* View in der CDB (Anzeige der TBS der CDB und der PDBs

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB 
     from cdb_data_files where TABLESPACE_NAME ='TEST' order by con_id

    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         3 TEST                                   10
         4 TEST                                   20


V$* Views 

Auch alle V$ Views haben eine Spalte CON_ID erhalten, mit der der entsprechende Container ermittelt werden kann.

Achtung: Bei Views die nicht CDB/PDB spezifische Informationen anzeigen, deren Informationen generell gelten (z. B. bei v$log oder v$logfile) wird die CON_ID = 0 verwendet. 
Es werden teilweise auch Informationen zur PDB$SEED (CON_ID=2) ausgegeben.

SQL> alter session set container=PDBZWEI;

SQL> -- Abfrage auf v$* View in einer PDB (Anzeige nur des TBS der akt. PDB)

SQL> select f.con_id, FILE#, t.TS#, t.name, f.bytes/1024/1024 MB 
     from v$datafile f, v$tablespace t     
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST';

    CON_ID      FILE#        TS# NAME                         MB
---------- ---------- ---------- -------------------- ----------
         4         30          6 TEST                         20

SQL> -- Abfrage auf v$log (Anzeige der Online Redo Logs mit CON_ID=0)

SQL> select CON_ID,GROUP#, THREAD# from  v$log;

    CON_ID     GROUP#    THREAD#
---------- ---------- ----------
         0          1          1
         0          2          1
         0          3          2
         0          4          2


SQL> alter session set container=CDB$ROOT;

SQL> -- Abfrage auf v$* View in einer CDB (Anzeige der TBS in der CDB und aller PDBs.
SQL> -- incl. der PDB$SEED)

SQL> select f.con_id, FILE#, t.TS#, t.name, f.bytes/1024/1024 MB 
     from v$datafile f, v$tablespace t     
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST' order by CON_ID;

    CON_ID      FILE#        TS# NAME                         MB
---------- ---------- ---------- -------------------- ----------
         1         31          6 TEST                          1
         2         33          5 TEST                          2
         3         29          6 TEST                         10
         4         30          6 TEST                         20


SQL> -- Abfrage auf v$log (Anzeige der Online Redo Logs mit CON_ID=0)

SQL> select CON_ID,GROUP#, THREAD# from  v$log;

    CON_ID     GROUP#    THREAD#
---------- ---------- ----------
         0          1          1
         0          2          1
         0          3          2
         0          4          2


*$ Tabellen

Bei Abfragen auf *$ Tabellen ist Vorsicht geboten, diese haben keine CON_ID, und zeigen in der Regel nur die Inhalte des aktuellen Kontextes an. 

SQL> alter session set container=PDBZWEI;

SQL> -- In der PDB wird nur der TBS der jeweiligen PDB angezeigt

SQL> select FILE#, TS# from file$ where FILE# >=29;

     FILE#        TS#
---------- ----------
        30          6

SQL> alter session set container=CDB$ROOT;

SQL> -- In der CDB wird nur der TBS der CDB angezeigt
SQL> select FILE#, TS# from file$ where FILE# >=29;

     FILE#        TS#
---------- ----------
        31          6
 

 

Abfragen auf nicht oder nur teilweise geöffnete PDBs

Wenn man sich mit der CDB verbinden um auch Informationen über die PDBs abzufragen, muss man nicht nur darauf achten welche Typen (CDB_* oder V$*) man in den Select Statements verwendet, sondern auch ob die PDB geöffnet ist. Dies ist vor allem im RAC Umfeld wichtig, da hier die PDB ggf. nicht in allen Instanzen geöffnet ist.

Generell gilt, Selects auf  V$* Views liefern zum Teil Ergebnisse, auch wenn die PDB geschlossen ist.
Selects auf CDB_* Views liefern in der Regel kein Ergebnis wenn die PDB komplett geschlossen ist.

Im RAC Umfeld kann es sein, dass z. B. bei Wartungsarbeiten auf einer CDB Instanz eine PDB geschlossen ist, aber auf minimum einer anderen CDB Instanz die PDB läuft. Hier kann der Session Parameter parallel_force_local helfen ein Ergebnis anzuzeigen. Ist dieser auf „FALSE“ gesetzt so kann die CDB die angeforderten Informationen von einer anderen Instanz auf der die PDB läuft zur Verfügung stellen. Ist eine PDB auf allen Instanzen gestoppt so können in der Regel bei den CDB_* Views die Informationen zur gestoppten PDB nicht abgefragt werden (Ausnahme z. B. CDB_PDBS).

parallel_force_local = TRUE führt in RAC-Systemen parallele Abfragen immer nur auf der lokalen Instanz aus. Bei FALSE werden parallel Queries auf allen RAC Knoten abgesetzt und somit können die CDB_* Views dort die benötigten Informationen sammeln.

Achtung: Ein Umsetzen des Parameters kann ggf. negative Auswirkungen auf die Applikationen bewirken, daher den Parameter nur innerhalb der Session setzen und ggf. nach dem entspr. SELECT wieder zurücknehmen.

SQL> alter session set container=CDB$ROOT;

SQL> select INST_ID,CON_ID,NAME,OPEN_MODE from gv$pdbs  order by CON_ID,INST_ID;

   INST_ID     CON_ID NAME                 OPEN_MODE
---------- ---------- -------------------- ----------
         1          2 PDB$SEED             READ ONLY
         2          2 PDB$SEED             READ ONLY
         1          3 PDBEINS              READ WRITE
         2          3 PDBEINS              READ WRITE
         1          4 PDBZWEI              READ WRITE
         2          4 PDBZWEI              READ WRITE


SQL> show parameter parallel_force_local

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
parallel_force_local                 boolean     TRUE


SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files 
     where TABLESPACE_NAME ='TEST' order by con_id;

    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         3 TEST                                   10
         4 TEST                                   20

SQL> select f.con_id,t.name, f.bytes/1024/1024 MB from v$datafile f, v$tablespace t 
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST' order by CON_ID;

    CON_ID NAME                         MB
---------- -------------------- ----------
         1 TEST                          1
         2 TEST                          2
         3 TEST                         10
         4 TEST                         20


SQL> -- Stoppen der PDBEINS nur auf der ersten/aktuellen Instanz

SQL> alter pluggable database PDBEINS close instances=('CDBA1');

Pluggable database altered.


SQL> select INST_ID,CON_ID,NAME,OPEN_MODE from gv$pdbs order by CON_ID,INST_ID;

   INST_ID     CON_ID NAME                 OPEN_MODE
---------- ---------- -------------------- ----------
         1          2 PDB$SEED             READ ONLY
         2          2 PDB$SEED             READ ONLY
         1          3 PDBEINS              MOUNTED
         2          3 PDBEINS              READ WRITE
         1          4 PDBZWEI              READ WRITE
         2          4 PDBZWEI              READ WRITE


SQL> -- der Tabelspace wird nicht mehr angezeigt, da die PDB auf der akt. Instanz gestoppt ist

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files 
     where TABLESPACE_NAME ='TEST' order by con_id;


    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         4 TEST                                   20

SQL> -- Die V$* views zeigen allerdings noch die Informationen zur geschlossenen PDB an

SQL> select f.con_id,t.name, f.bytes/1024/1024 MB from v$datafile f, v$tablespace t 
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST' order by CON_ID;

    CON_ID NAME                         MB
---------- -------------------- ----------
         1 TEST                          1
         2 TEST                          2
         3 TEST                         10
         4 TEST                         20


SQL> -- setzen des Session Parameters
SQL> alter session set parallel_force_local=false;

SQL> -- der Tabelspace auf der aktuellen Instanz gestoppten PDB wird mit angezeigt!

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files 
     where TABLESPACE_NAME ='TEST' order by con_id;

    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         3 TEST                                   10
         4 TEST                                   20

SQL> select f.con_id,t.name, f.bytes/1024/1024 MB from v$datafile f, v$tablespace t 
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST' order by CON_ID;

    CON_ID NAME                         MB
---------- -------------------- ----------
         1 TEST                          1
         2 TEST                          2
         3 TEST                         10
         4 TEST                         20


SQL> alter session set parallel_force_local=true;

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files 
     where TABLESPACE_NAME ='TEST' order by con_id;

    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         4 TEST                                   20


SQL> -- Wird die PDB komplett geschlossen, so zeigen die CDB_*-Views die PDB nicht 
SQL> -- mehr an, egal wie der Parameter 'parallel_force_local' gesetzt ist

SQL> alter pluggable database PDBEINS close instances=ALL;
SQL> alter session set parallel_force_local=false;

SQL> select CON_ID, TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files 
     where TABLESPACE_NAME ='TEST' order by con_id;


    CON_ID TABLESPACE_NAME                        MB
---------- ------------------------------ ----------
         1 TEST                                    1
         4 TEST                                   20

SQL> select f.con_id,t.name, f.bytes/1024/1024 MB from v$datafile f, v$tablespace t 
     where f.TS# = t.TS# and f.con_id= t.con_id  and t.name = 'TEST' order by CON_ID;


    CON_ID NAME                         MB
---------- -------------------- ----------
         1 TEST                          1
         2 TEST                          2
         3 TEST                         10
         4 TEST                         20

 

Beispielskript zum Monitoring oder für DBA-Abfragen

Das folgende Beispielskript kann als Vorlage für das Monitoring oder für DBA-Abfragen verwendet werden. Im ersten Abschnitt wird geprüft, ob man an einer non-CDB, CDB oder PDB angemeldet ist, incl. Exceptionhandling, um das Skript zu beenden wenn die Oracle Version < 12 ist (dort gibt es die CDB-Views sowie die Spalten CON_ID noch nicht).
Nur wenn man an einer CDB angemeldet ist, wird über Bind Variable v_cdb bzw. die substitution Variable IF_CDB, die im ersten Abschnitt ermittelt wird, gesteuert ob die Spalte „Container“ angezeigt wird oder nicht (NOPRINT). Die Anzeige der Spalte con_id wird im Skript mit NOPRINT unterdrückt.
Zudem wird der aktuelle Wert des Session Parameter parallel_force_local in der substitution Variable col_old_param zwischengespeichert.
Nur wenn der Session Parameter parallel_force_local auf dem Wert true steht wird dieser auf false geändert.
Anschließend werden zwei Beispiel Abfragen ausgegeben. 
Beim ersten Select wird auf einer CDB oder PDB angemeldet als erste Spalte der Container-Name ausgeben. Auf non-CDB Datenbanken wird die Spalte Container unterdrückt.
Beim zweiten Select wird auf einer CDB oder PDB angemeldet die Spalte „Tablespace“ um die Information des Containers ergänzt.
Zum Abschluss wird der Session Parameter parallel_force_local wieder auf den ursprünglichen Wert und alle Colums auf printable gesetzt, damit nachfolgende Abfragen nicht beeinflusst werden. 

Beispiel Skript:

cat cdb_test.sq

set termout on

set pagesize 60
set linesize 300

SET verify  off
set feedback off

set heading off
set serveroutput o

-- +----------------------------------------------------------------------------------------+
-- | check if DB is CDB/PDB
-- +----------------------------------------------------------------------------------------+
-- define bind varable
variable v_cdb varchar2(8);
-- get value for bind variable incl. exception handling for version below Oracle 12
declare
  v_statement varchar2(2000);
  no_con_id_error EXCEPTION;
  no_usernv_error EXCEPTION;
  PRAGMA exception_init ( no_usernv_error, -02003 );
  PRAGMA exception_init ( no_con_id_error, -00904 );
begin
  v_statement:='select decode((decode(CDB,''YES'',1,0) * sys_context (''USERENV'',''CON_ID'')), 1, '''',''NOPRINT'' ) from V$DATABASE';
  execute immediate v_statement into :v_cdb;
exception
  when no_con_id_error or no_usernv_error then
    :v_cdb := 'NOPRINT';
  when others then
    raise;
end;
/
-- define column col_CDB with new substitution variable IF_CDB
column col_CDB new_value IF_CDB noprint
-- pass the bind variable into new substitution variable IF_CDB using a query
select :v_cdb col_CDB from dual

-- save actual value for parallel_force_local
column col_old_param new_value col_old_param noprint
select VALUE col_old_param from v$parameter where lower(name) = 'parallel_force_local'

prompt actual value for parallel_force_local=&col_old_param
set feedback of


-- set parallel_force_local=FALSE only if it is TRUE
declare
  v_alter_session varchar2(200);
begin
  begin
    select 'alter session set parallel_force_local=FALSE' into v_alter_session from v$parameter where lower(name) =  'parallel_force_local' and upper('&col_old_param') = 'TRUE';
  exception
    when no_data_found then
      v_alter_session := '';
  end;
  if v_alter_session is not null then
    dbms_output.put_line(v_alter_session);
    execute immediate v_alter_session;
  else
    dbms_output.put_line('parallel_force_local is already FALSE');
  end if;
end;


-- show actual value for session parameter 'parallel_force_local'
select name||'='||VALUE test from v$parameter where lower(name) =  'parallel_force_local'


col con_id          noprint
-- do not display the column "container", if you run the script in a non-CDB 
col container       format a20 heading 'Container'  justify c word_wrapped &IF_CDB
col TABLESPACE_NAME format a40 heading 'Tablespace' justify c word_wrapped
col MB              format 999999999 heading 'MB'
set heading o

-- only for CDB or PDB, display the container name as first column 
SELECT con_id, ( select c.NAME from v$containers c where c.con_id=a.con_id ) container,
       TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files a
 where TABLESPACE_NAME ='TEST' order by con_id

-- only for CDB or PDB, display the container name and the tablespace name, 
-- otherwise only the tablespace name
SELECT con_id, 
       ( select c.NAME || ':' from v$containers c where c.con_id=a.con_id and c.con_id>0) ||                  
       TABLESPACE_NAME TABLESPACE_NAME, bytes/1024/1024 MB from cdb_data_files a
 where TABLESPACE_NAME ='TEST' order by con_id

set heading off

-- set parallel_force_local to saved value, only if it is different to saved value
declare
  v_alter_session varchar2(200);
begin
  begin
    select 'alter session set parallel_force_local=&col_old_param' into v_alter_session from v$parameter where lower(name) =  'parallel_force_local' and upper(VALUE) != upper('&col_old_param');
  exception
    when no_data_found then
      v_alter_session := '';
  end;
  if v_alter_session is not null the
    dbms_output.put_line(v_alter_session);
    execute immediate v_alter_session;
  else
    dbms_output.put_line('parallel_force_local is already FALSE');
  end if;
end;


-- show actual value for session parameter 'parallel_force_local'
select name||'='||VALUE test from v$parameter where lower(name) =  'parallel_force_local'

-- set all columns to "printable" otherwise they won’t be visible 
-- in further selects, reset parameter
col con_id          print
col col_CDB         print
col container       print
col col_old_param   prin

set heading on


Ausgabe des Beispielskriptes ausgeführt auf einer CDB mit parallel_force_local=TRUE

SQL> @cdb_test.sql


actual value for parallel_force_local=TRUE
alter session set parallel_force_local=FALSE

parallel_force_local=FALSE

     Container                      Tablespace                        MB
-------------------- ---------------------------------------- ----------
CDB$ROOT             TEST                                              1
PDBZWEI              TEST                                             20

               Tablespace                        MB
---------------------------------------- ----------
CDB$ROOT:TEST                                     1
PDBZWEI:TEST                                     20

alter session set parallel_force_local=TRUE

parallel_force_local=TRUE


Ausgabe des Beispielskriptes ausgeführt auf einer CDB mit parallel_force_local=FALSE:

SQL> @cdb_test.sql


actual value for parallel_force_local=FALSE
parallel_force_local is already FALSE

parallel_force_local=FALSE

     Container                      Tablespace                        MB
-------------------- ---------------------------------------- ----------
CDB$ROOT             TEST                                              1
PDBZWEI              TEST                                             20
               Tablespace                        MB
---------------------------------------- ----------
CDB$ROOT:TEST                                     1
PDBZWEI:TEST                                     20

parallel_force_local is already FALSE

parallel_force_local=FALSE


Ausgabe des Beispielskriptes ausgeführt auf einer non-CDB: 

SQL> @cdb_test.sql


actual value for parallel_force_local=TRUE
alter session set parallel_force_local=FALSE

parallel_force_local=FALSE

               Tablespace                        MB
---------------------------------------- ----------
TEST                                             40

               Tablespace                        MB
---------------------------------------- ----------
TEST                                             40

alter session set parallel_force_local=TRUE

 

Fazit

Wird bei den Abfragen darauf geachtet welche Art von Views verwendet werden, und ggf. der Session Parameter parallel_force_local umgesetzt so können auf der CDB auch die dort laufenden PDBs mit überwacht werden. 
Ältere Statements die noch auf *$ Tabellen basieren sollten nicht mehr verwendet werden!
Das oben aufgeführte Beispiel Skript lässt sich sowohl auf einer non-CDB, CDB oder PDB-Datenbank ausführen.
Das erste der SELECT-Statements ist eher für Skripte, die der DBA ausführt geeignet, da hier ggf. die Container Spalte ein- oder ausgeblendet wird.
Das zweite SELECT-Statement findet eher bei Überwachungen den Einsatz, da hier die Spaltenanzahl gleich bleibt und ggf. nur dem Tablespacenamen der Container vorangestellt wird.


to be continued  

Jede Menge Know-how für Sie!

In unserer Know-How Datenbank finden Sie mehr als 300 ausführliche Beiträge zu den Oracle-Themen wie DBA, SQL, PL/SQL, APEX und vielem mehr.
Hier erhalten Sie Antworten auf Ihre Fragen.