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.