In some of my previous blogs, I showed how we were migrating on-premise databases to the ExaCC using Oracle ZDM at one of our customer. I also explained in some article how it was important in some case to relocate the PDB in a final CDB. Relocating a PDB on ExaCC is usually done using dbaascli pdb relocate command. The only drawback of this way, is that dbaascli is doing a copy of the datafiles during the relocate, which for a huge terabytes database, is taking time. In oder to minimise the downtime, I had to study another way to relocate the pdb, by moving the datafiles instead of doing a copy, and could manually relocate it in a few minutes. In this blog, I would like to share with you my findings and hoping it can help some of you.
Purpose
In my case, I would like to relocate PDB, named PDB_DRY_999T, from the source CDB, named CDB_CHZ2, into the target CDB, named CDBTGT_CHZ2.
Source CDB information:
oracle@exacc-cl01n1:~/ [CDB1 (CDB$ROOT)] CDB1 *********************************** INSTANCE_NAME : CDB1 DB_NAME : CDB DB_UNIQUE_NAME : CDB_CHZ2 STATUS : OPEN READ WRITE LOG_MODE : ARCHIVELOG USERS/SESSIONS : 2/9 DATABASE_ROLE : PRIMARY FLASHBACK_ON : YES FORCE_LOGGING : YES VERSION : 19.26.0.0.0 CDB_ENABLED : YES PDBs : PDB_DRY_999T PDB$SEED ***********************************
Target CDB information:
oracle@exacc-cl01n1:~/ [CDB1 (CDB$ROOT)] CDBTGT1 *********************************** INSTANCE_NAME : CDBTGT1 DB_NAME : CDBTGT DB_UNIQUE_NAME : CDBTGT_CHZ2 STATUS : OPEN READ WRITE LOG_MODE : ARCHIVELOG USERS/SESSIONS : 2/10 DATABASE_ROLE : PRIMARY FLASHBACK_ON : YES FORCE_LOGGING : YES VERSION : 19.26.0.0.0 CDB_ENABLED : YES PDBs : PDB$SEED ***********************************
Knowing we are using TDE with ExaCC, it will be needed to export and import the encryption key. I’m using the FORCE option during export and import of the encryption key because I’m using AUTO LOGIN.
Export the encryption key on the PDB from the source CDB_CHZ2
Let’s connect to the PDB and check encryption.
oracle@exacc-cl01n1:~/ [CDB1 (CDB$ROOT)] sqh
SQL*Plus: Release 19.0.0.0.0 - Production on Fri Aug 29 10:05:55 2025
Version 19.26.0.0.0
Copyright (c) 1982, 2024, Oracle. All rights reserved.
Connected to:
Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production
Version 19.26.0.0.0
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE NO
SQL> alter session set container=PDB_DRY_999T;
Session altered.
SQL> set lines 300 pages 500
SQL> col WRL_PARAMETER format a30
SQL> select WRL_PARAMETER, WRL_TYPE,WALLET_TYPE, status from V$ENCRYPTION_WALLET;
WRL_PARAMETER WRL_TYPE WALLET_TYPE STATUS
------------------------------ -------------------- -------------------- ------------------------------
FILE AUTOLOGIN OPEN
SQL> col status for a20
SQL> col name for a30
SQL> select a.con_id, name, status, keystore_mode from v$pdbs a, v$encryption_wallet b where a.con_id=b.con_id;
CON_ID NAME STATUS KEYSTORE
---------- ------------------------------ -------------------- --------
3 PDB_DRY_999T OPEN UNITED
We are in united mode so all cdb and pdb encryption keys are stored in same keystore.
If we try to export the encryption key, we will get an ORA-46659 error:
SQL> ADMINISTER KEY MANAGEMENT EXPORT ENCRYPTION KEYS WITH SECRET "********************" to '/home/oracle/mwagner/TDE_for_unplug/PDB_DRY_999T_export.p12' FORCE keystore IDENTIFIED BY "********************"; ADMINISTER KEY MANAGEMENT EXPORT ENCRYPTION KEYS WITH SECRET "********************" to '/home/oracle/mwagner/TDE_for_unplug/PDB_DRY_999T_export.p12' FORCE keystore IDENTIFIED BY "********************" * ERROR at line 1: ORA-46659: master keys for the given PDB not found
Let’s create a new master key for the PDB.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
3 PDB_DRY_999T READ WRITE NO
SQL> ADMINISTER KEY MANAGEMENT SET KEY FORCE KEYSTORE IDENTIFIED BY "********************" WITH BACKUP USING 'pre-unplug-PDB_DRY_999T_20250829';
keystore altered.
SQL>
And now we can export the pdb encryption key:
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
3 PDB_DRY_999T READ WRITE NO
SQL> ADMINISTER KEY MANAGEMENT EXPORT ENCRYPTION KEYS WITH SECRET "********************" to '/home/oracle/mwagner/TDE_for_unplug/PDB_DRY_999T_export_pdb_level.p12' FORCE keystore IDENTIFIED BY "********************";
keystore altered.
SQL>
Unplug PDB_DRY_999T PDB from source CDB
Let’s close the PDB and run ALTER PLUGGABLE DATABASE <PDB_NAME> UNPLUG INTO '<XML_FILE>' command:
SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
CDB1
SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE NO
SQL> alter pluggable database PDB_DRY_999T close instances=all;
Pluggable database altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T MOUNTED
SQL> ALTER PLUGGABLE DATABASE PDB_DRY_999T UNPLUG INTO '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml';
Pluggable database altered.
SQL> !ls -ltrh /home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml
-rw-r--r-- 1 oracle asmdba 2.7M Aug 29 10:50 /home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml
SQL>
In the alert log we would see some warnings to inform that the encryption key will need to be imported. Otherwise the pdb can only be opened in restricted mode:
PDB_DRY_999T(3):KZTDE:WARNING: Detected that PDB needs to import keys from source. PDB can only open in restricted mode until import.
This is of course makes sense.
Drop the PDB but keep datafiles!!!
As we are going to relocate the PDB moving the datafiles, it is VERY IMPORTANT to drop the PDB by KEEPING the datafiles.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T MOUNTED
SQL> drop pluggable database PDB_DRY_999T keep datafiles;
Pluggable database dropped.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
SQL>
From the alert log file we can see that only the temporary file are dropped. This file will be recreated again by the oracle plug command.
2025-08-29T10:57:26.137914+02:00
Deleted Oracle managed file +DATAC2/CDB_CHZ2/3D6E40CA5F218D4EE063181FA10A38B0/TEMPFILE/temp.26231.1210348651
2025-08-29T10:57:26.168729+02:00
Stopped service pdb_dry_999t
If we connect to the ASM, we will see that the datafiles are still present. I still have 7.45 TB of datafiles for my PDB.
ASMCMD> du PDB_DRY_999T Used_MB Mirror_used_MB 7809404 23428212 ASMCMD>
Check compatibility for plugging the PDB in the target CDB
I will now connect to the target CDB, and check if the PDB is compatible to be plug in.
I will run following SQL command to do so:
set serveroutput on
DECLARE
compatible BOOLEAN := FALSE;
BEGIN
compatible := DBMS_PDB.CHECK_PLUG_COMPATIBILITY(
pdb_descr_file => '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml');
if compatible then
DBMS_OUTPUT.PUT_LINE('Is pluggable PDB_DRY_999T compatible? YES');
else DBMS_OUTPUT.PUT_LINE('Is pluggable PDB_DRY_999T compatible? NO');
end if;
END;
Let’s connect to the target CDB and check:
oracle@exacc-cl01n1:~/ [CDB1 (CDB$ROOT)] CDBTGT1
***********************************
INSTANCE_NAME : CDBTGT1
DB_NAME : CDBTGT
DB_UNIQUE_NAME : CDBTGT_CHZ2
STATUS : OPEN READ WRITE
LOG_MODE : ARCHIVELOG
USERS/SESSIONS : 2/10
DATABASE_ROLE : PRIMARY
FLASHBACK_ON : YES
FORCE_LOGGING : YES
VERSION : 19.26.0.0.0
CDB_ENABLED : YES
PDBs : PDB$SEED
***********************************
PDB color: pdbname=mount, pdbname=open read-write, pdbname=open read-only
Statustime: 2025-08-29 11:07:00
oracle@exacc-cl01n1:~/ [CDBTGT1 (CDB$ROOT)] sqh
SQL*Plus: Release 19.0.0.0.0 - Production on Fri Aug 29 11:07:02 2025
Version 19.26.0.0.0
Copyright (c) 1982, 2024, Oracle. All rights reserved.
Connected to:
Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production
Version 19.26.0.0.0
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
SQL> set serveroutput on
SQL> DECLARE
2 compatible BOOLEAN := FALSE;
3 BEGIN
4 compatible := DBMS_PDB.CHECK_PLUG_COMPATIBILITY(
5 pdb_descr_file => '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml');
6 if compatible then
7 DBMS_OUTPUT.PUT_LINE('Is pluggable PDB_DRY_999T compatible? YES');
8 else DBMS_OUTPUT.PUT_LINE('Is pluggable PDB_DRY_999T compatible? NO');
9 end if;
10 END;
11 /
Is pluggable PDB_DRY_999T compatible? YES
PL/SQL procedure successfully completed.
SQL>
The PDB can be plug in my target CDB.
Import PDB encryption key in the target CDB keystore
We first need to import the PDB encryption key in the CDB, otherwise the plugging command would failed with ORA-28374 error:
SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
CDBTGT1
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT
SQL> create pluggable database PDB_DRY_999T using '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml' move;
create pluggable database PDB_DRY_999T using '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml' move
*
ERROR at line 1:
ORA-28374: typed master key not found in wallet
So let’s import the PDB encryption key into the CDB:
SQL> select instance_name from v$instance; INSTANCE_NAME ---------------- CDBTGT1 SQL> show con_name CON_NAME ------------------------------ CDB$ROOT SQL> ADMINISTER KEY MANAGEMENT IMPORT ENCRYPTION KEYS WITH SECRET "********************" from '/home/oracle/mwagner/TDE_for_unplug/PDB_DRY_999T_export_pdb_level.p12' FORCE keystore IDENTIFIED BY "********************" with backup using 'pre-plug-PDB_DRY_999T_20250829'; keystore altered. SQL>
Plug the PDB in the target CDB CDBTGT_CHZ2
Now we can plug the PDB in the target CDB:
SQL> !date
Fri Aug 29 11:27:36 CEST 2025
SQL> select instance_name from v$instance;
INSTANCE_NAME
----------------
CDBTGT1
SQL> create pluggable database PDB_DRY_999T using '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml' move;
Pluggable database created.
SQL> !date
Fri Aug 29 11:30:36 CEST 2025
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T MOUNTED
SQL>
We can see that instead of taking several hours to relocate the PDB with dbaascli, we took about 3 minutes to relocate the PDB moving the datafiles.
We can check the alert log file for any errors. In my case, I can see the completed operation:
Completed: create pluggable database PDB_DRY_999T using '/home/oracle/mwagner/unplug_pdb/PDB_DRY_999T.xml' move
2025-08-29T11:30:52.168375+02:00
Checking the ASM I can see that the source datafile directory is now empty:
ASMCMD> pwd +DATAC2 ASMCMD> du PDB_DRY_999T Used_MB Mirror_used_MB 48780 146340 ASMCMD> cd PDB_DRY_999T ASMCMD> ls CHANGETRACKING/ DATAGUARDCONFIG/ TEMPFILE/ controlfile/ datafile/ onlinelog/ password/ ASMCMD> cd datafile ASMCMD> pwd +DATAC2/PDB_DRY_999T/datafile ASMCMD> ls ASMCMD>
Start the PDB
Let’s start the PDB.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T MOUNTED
SQL> alter pluggable database PDB_DRY_999T open instances=all;
Warning: PDB altered with errors.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE YES
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE YES
The PDB is started in restricted mode. This was given as warning in the alert log during the unplug, by informing that the encryption key needs to be imported in the PDB in order to open it without any restriction.
Let’s check the violations.
SQL> select name,cause,type,message,status from PDB_PLUG_IN_VIOLATIONS where status 'RESOLVED';
NAME CAUSE TYPE MESSAGE STATUS
--------------- -------------------------------------------------- --------- ------------------------------------------------------------------------------------------------------------------------ --------------------
PDB_DRY_999T Parameter WARNING CDB parameter processes mismatch: Previous 2048 Current 1000 PENDING
PDB_DRY_999T Parameter WARNING CDB parameter sga_max_size mismatch: Previous 4G Current 9G PENDING
PDB_DRY_999T Parameter WARNING CDB parameter archive_lag_target mismatch: Previous 1800 Current 0 PENDING
PDB_DRY_999T Parameter WARNING CDB parameter cluster_database mismatch: Previous FALSE Current TRUE PENDING
PDB_DRY_999T Parameter WARNING CDB parameter db_lost_write_protect mismatch: Previous 'typical' Current 'NONE' PENDING
PDB_DRY_999T Parameter WARNING CDB parameter inmemory_force mismatch: Previous 'cellmemory_level' Current 'DEFAULT' PENDING
PDB_DRY_999T Parameter WARNING CDB parameter wallet_root mismatch: Previous '/var/opt/oracle/dbaas_acfs/CDB/wallet_root' Current '/var/opt/oracle/dbaa PENDING
s_acfs/CDBTGT/wallet_root'
PDB_DRY_999T Parameter WARNING CDB parameter distributed_lock_timeout mismatch: Previous 360 Current 60 PENDING
PDB_DRY_999T Parameter WARNING CDB parameter unified_audit_common_systemlog mismatch: Previous 'LOCAL5.INFO' Current NULL PENDING
PDB_DRY_999T Parameter WARNING CDB parameter enable_ddl_logging mismatch: Previous TRUE Current FALSE PENDING
PDB_DRY_999T Parameter WARNING CDB parameter awr_pdb_max_parallel_slaves mismatch: Previous 2 Current 10 PENDING
PDB_DRY_999T Parameter WARNING CDB parameter awr_snapshot_time_offset mismatch: Previous 1000000 Current 0 PENDING
PDB_DRY_999T OPTION WARNING Database option CONTEXT mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option DV mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option OLS mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option ORDIM mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option OWM mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option SDO mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T Wallet Key Needed ERROR PDB needs to import keys from source. PENDING
19 rows selected.
SQL>
Here we have:
PDB_DRY_999T Wallet Key Needed ERROR PDB needs to import keys from source.
Import encryption key into the PDB
Let’s connect to the PDB and import the encryption key:
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE YES
SQL> alter session set container=PDB_DRY_999T;
Session altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
3 PDB_DRY_999T READ WRITE YES
SQL> ADMINISTER KEY MANAGEMENT IMPORT ENCRYPTION KEYS WITH SECRET "********************" from '/home/oracle/mwagner/TDE_for_unplug/PDB_DRY_999T_export_pdb_level.p12' FORCE keystore IDENTIFIED BY "********************" with backup using 'post-plug-PDB_DRY_999T_20250829';
keystore altered.
SQL>
Restart the PDB
Let’s restart the PDB and check the status.
SQL> alter session set container=cdb$root;
Session altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE YES
SQL> alter pluggable database PDB_DRY_999T close instances=all;
Pluggable database altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T MOUNTED
SQL> alter pluggable database PDB_DRY_999T open instances=all;
Pluggable database altered.
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDB_DRY_999T READ WRITE NO
SQL>
We can see that the PDB is now opened without any restriction. Let’s check the PDB violations:
SQL> show pdbs
CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
3 PDB_DRY_999T READ WRITE NO
SQL> col name for a15
SQL> col message for a120
SQL> col cause for a50
SQL> select name,cause,type,message,status from PDB_PLUG_IN_VIOLATIONS where status 'RESOLVED';
NAME CAUSE TYPE MESSAGE STATUS
--------------- -------------------------------------------------- --------- ------------------------------------------------------------------------------------------------------------------------ --------------------
PDB_DRY_999T OPTION WARNING Database option CONTEXT mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option DV mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option OLS mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option ORDIM mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option OWM mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
PDB_DRY_999T OPTION WARNING Database option SDO mismatch: PDB installed version NULL. CDB installed version 19.0.0.0.0. PENDING
6 rows selected.
SQL>
There is no more violations.
Check the PDB
Check datafiles location:
SQL> select name from v$datafile where rownum select count(*) from v$datafile where name like '+DATAC2/CDBTGT_CHZ2/3D6E40CA5F218D4EE063181FA10A38B0/DATAFILE%';
COUNT(*)
----------
2830
SQL> select count(*) from v$datafile where name not like '+DATAC2/CDBTGT_CHZ2/3D6E40CA5F218D4EE063181FA10A38B0/DATAFILE%';
COUNT(*)
----------
0
SQL>
All good, PDB datafiles are in the expected ASM folder.
Check that there is no invalid objects:
SQL> select count(*) from dba_invalid_objects;
COUNT(*)
----------
0
And finally check that all tablespace are encrypted:
SQL> select count(*) from cdb_tablespaces a, (select x.con_id, y.ENCRYPTIONALG, x.name from V$TABLESPACE x, V$ENCRYPTED_TABLESPACES y
2 where x.ts#=y.ts# and x.con_id=y.con_id) b where a.con_id=b.con_id(+) and a.tablespace_name=b.name(+) and b.ENCRYPTIONALG='AES128';
COUNT(*)
----------
2831
SQL> select count(*) from cdb_tablespaces a, (select x.con_id, y.ENCRYPTIONALG, x.name from V$TABLESPACE x, V$ENCRYPTED_TABLESPACES y
2 where x.ts#=y.ts# and x.con_id=y.con_id) b where a.con_id=b.con_id(+) and a.tablespace_name=b.name(+) and b.ENCRYPTIONALG is null;
COUNT(*)
----------
0
SQL>
The PDB is as well registered in the listener:
oracle@exacc-cl01n1:~/ [CDBTGT1 (CDB$ROOT)] export TNS_ADMIN=/u02/app/oracle/product/19.0.0.0/dbhome_1/network/admin/listener_test/ oracle@exacc-cl01n1:~/ [CDBTGT1 (CDB$ROOT)] lsnrctl status LISTENER_TEST| grep -i PDB_DRY_999T Service "pdb_dry_999t.domain.com" has 1 instance(s). oracle@exacc-cl01n1:~/ [CDBTGT1 (CDB$ROOT)]
To wrap up
Here we have a good alternative for relocating a PDB on ExaCC avoiding a copy of the datafiles. This will have the benefit to limit the maintenance windows.