ORA-1031 during Oracle Transportable Tablespace Import

I really do like the idea of transportable tablespaces in the Oracle database world for several reasons. It is a good feature for migrating data from one system to another, it allows to skip lenghty upgrade procedures, it can be used to change the OS platform and it can reduce downtime by utilizing incremental updates. In a current project we are using this feature to migrate to AWS. Please don’t ask why, in my opinion there are other cloud providers, that would fit better to host an Oracle database. But that’s only a side note. AWS provides Oracle databases as a managed service, somehow similar to Oracle Autonomous Database. You don’t get SYSDBA access to the database, there is only an administrative account with restricted privileges and access to some AWS-provided packages. Since the database in question is roughly 6.5TB in size, Datapump is not the first approach to migrate the data. But AWS allows to use the Transportable Tablespace feature as well. So we decided to go that way.

We followed these steps using the XTTS V4 scripts from Oracle to generate the initial backup and an incremental backup with the tablespaces in read only state. Finally we did a metadata and a tablspace export. During the final import at the AWS end, we run into an issue that I could also reproduce in the old on-prem world. Before starting a transportable tablespace import some prerequisites must be fullfilled. The TTS import creates all objects, that are in the transported tablespaces. Basically we talk about tables and indexes. Nothing else has segments in a tablespace. And nothing else is in the TTS export file. Not quite true, dependend objects are part of it, like grants, triggers etc. So we need to create all the users, that own segments in these tablespaces beforehand since their definition is not part of the TTS export. I did that with a simple “create user XYZ” statement. Also we might need to create all sequences since ID columns might use sequences as a default value. I simply took these from the full metadata export using these IMPDP parameters.

full=y
include=sequence

Then I started the TTS import. But unfortunately it did not succeed.

oracle@olca0046:~/xtts_ref> impdp parfile=imp_ts.par

Import: Release 19.0.0.0.0 - Production on Tue Jan 9 14:08:02 2024
Version 19.19.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

Connected to: Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
09-JAN-24 14:08:05.558: W-1 Startup took 0 seconds
09-JAN-24 14:08:10.276: W-1 Master table "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" successfully loaded/unloaded
09-JAN-24 14:08:15.467: Starting "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01":  system/********@olca0046:1523/xttback parfile=imp_ts.par
09-JAN-24 14:08:16.050: W-1 Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
09-JAN-24 14:08:20.865: W-1      Completed 1 PLUGTS_BLK objects in 4 seconds
09-JAN-24 14:08:20.865: W-1 Processing object type TRANSPORTABLE_EXPORT/TYPE/TYPE_SPEC
09-JAN-24 14:08:22.262: W-1      Completed 1 TYPE objects in 0 seconds
09-JAN-24 14:08:22.262: W-1 Processing object type TRANSPORTABLE_EXPORT/PROCACT_INSTANCE
09-JAN-24 14:08:24.473: W-1      Completed 1 PROCACT_INSTANCE objects in 1 seconds
09-JAN-24 14:08:24.473: W-1 Processing object type TRANSPORTABLE_EXPORT/TABLE
09-JAN-24 14:12:52.460: ORA-39083: Object type TABLE:"EC_NDB"."NDB_GETAG_CACHE" failed to create with error:
ORA-01031: insufficient privileges

CREATE TABLE "EC_NDB"."NDB_GETAG_CACHE" ("ID" NUMBER NOT NULL ENABLE, "KALK_ID" NUMBER, "DB_USER" VARCHAR2(50 BYTE) DEFAULT user NOT NULL ENABLE, "REQUEST_START" DATE, "REQUEST_END" DATE, "REQUEST" VARCHAR2(50 BYTE), "STATUS" VARCHAR2(10 BYTE), "ERROR" VARCHAR2(1000 BYTE), "RESPONSE" "SYS"."XMLTYPE" ) SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255  NOCOMPRESS LOGGING STORAGE(SEG_FILE 34 SEG_BLOCK 87786 OBJNO_REUSE 67318 INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) TABLESPACE "EC_TBS_SYS"  XMLTYPE COLUMN "RESPONSE" STORE AS SECUREFILE BINARY XML ( TABLESPACE "EC_TBS_LOB" ENABLE STORAGE IN ROW CHUNK 8192 CACHE  NOCOMPRESS  KEEP_DUPLICATES  STORAGE(SEG_FILE 27 SEG_BLOCK 1505 OBJNO_REUSE 67321 INITIAL 106496 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)  INDEX ( INITRANS 2 MAXTRANS 255 STORAGE(SEG_FILE 27 SEG_BLOCK 1522 OBJNO_REUSE 67322 INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)) ) ALLOW NONSCHEMA DISALLOW ANYSCHEMA

09-JAN-24 14:12:52.460: ORA-39083: Object type TABLE:"EC_UK"."UK_XCH_XML_IMPORT" failed to create with error:
ORA-01031: insufficient privileges

Failing sql is:
CREATE TABLE "EC_UK"."UK_XCH_XML_IMPORT" ("ID" NUMBER(15,0) NOT NULL ENABLE, "XML_CONTENT" "SYS"."XMLTYPE" , "LOADINGDATE" DATE DEFAULT sysdate, "PROCESSDATE" DATE, "STATUS" NUMBER DEFAULT 100 NOT NULL ENABLE, "IFI_ID" NUMBER(15,0), "STATUSTEXT" VARCHAR2(1000 BYTE), "MSG_ID" VARCHAR2(100 BYTE)) SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255  NOCOMPRESS LOGGING STORAGE(SEG_FILE 34 SEG_BLOCK 87802 OBJNO_REUSE 67394 INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) TABLESPACE "EC_TBS_SYS"  XMLTYPE COLUMN "XML_CONTENT" STORE AS SECUREFILE BINARY XML ( TABLESPACE "EC_TBS_LOB" ENABLE STORAGE IN ROW CHUNK 8192 NOCACHE LOGGING  NOCOMPRESS  KEEP_DUPLICATES  STORAGE(SEG_FILE 27 SEG_BLOCK 1545 OBJNO_REUSE 67395 INITIAL 106496 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)  INDEX ( INITRANS 2 MAXTRANS 255 STORAGE(SEG_FILE 27 SEG_BLOCK 1562 OBJNO_REUSE 67396 INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)) ) ALLOW NONSCHEMA DISALLOW ANYSCHEMA
[...]

There were some more tables like these which failed to import. My first assumption was, all these tables have LOBs in it. But there are other tables with LOBs which were imported successfully. So that was not the root cause. On the other hand, all of these failing tables have columns of type XMLTYPE. So I investigated in that direction and found MOS note 2224183.1: “ORA-39083 / ORA-01031 ‘ insufficient privileges’ Error Using Data Pump to Import Data when the Schema Used to Import has the Necessary Privileges”. The note explains the behaviour. For importing normal tables it is sufficient to have “soft” privileges to create tables in any schema. But for these tables with XMLTYPE columns, it is neccessary to act as the owning user. Therefore the user must be granted the “CREATE TABLE” privilege. Having that, I did grant the privilege to the users in qestion and re-run the import again.

oracle@olca0046:~/xtts_ref> impdp parfile=imp_ts.par

Import: Release 19.0.0.0.0 - Production on Tue Jan 9 15:12:57 2024
Version 19.19.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

Connected to: Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Legacy Mode Active due to the following parameters:
Legacy Mode Parameter: "statistics=none" Location: Parameter File, ignored.
09-JAN-24 15:13:09.061: W-1 Startup took 1 seconds
09-JAN-24 15:13:17.169: W-1 Master table "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" successfully loaded/unloaded
09-JAN-24 15:13:22.628: Starting "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01":  system/********@olca0046:1523/xttback parfile=imp_ts.par
09-JAN-24 15:13:23.180: W-1 Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
09-JAN-24 15:13:31.874: W-1      Completed 1 PLUGTS_BLK objects in 8 seconds
09-JAN-24 15:13:31.874: W-1 Processing object type TRANSPORTABLE_EXPORT/TYPE/TYPE_SPEC
09-JAN-24 15:13:33.209: W-1      Completed 1 TYPE objects in 0 seconds
09-JAN-24 15:13:33.209: W-1 Processing object type TRANSPORTABLE_EXPORT/PROCACT_INSTANCE
09-JAN-24 15:13:35.363: W-1      Completed 1 PROCACT_INSTANCE objects in 1 seconds
09-JAN-24 15:13:35.363: W-1 Processing object type TRANSPORTABLE_EXPORT/TABLE
09-JAN-24 15:18:25.860: W-1      Completed 5756 TABLE objects in 289 seconds
09-JAN-24 15:18:25.860: W-1 Processing object type TRANSPORTABLE_EXPORT/GRANT/OWNER_GRANT/OBJECT_GRANT
09-JAN-24 15:20:12.815: W-1      Completed 92252 OBJECT_GRANT objects in 106 seconds
09-JAN-24 15:20:12.815: W-1 Processing object type TRANSPORTABLE_EXPORT/GRANT/WITH_GRANT_OPTION/OBJECT_GRANT
09-JAN-24 15:20:14.143: W-1      Completed 61 OBJECT_GRANT objects in 0 seconds
09-JAN-24 15:20:14.143: W-1 Processing object type TRANSPORTABLE_EXPORT/GRANT/CROSS_SCHEMA/OBJECT_GRANT
09-JAN-24 15:20:15.902: W-1      Completed 455 OBJECT_GRANT objects in 1 seconds
09-JAN-24 15:20:15.902: W-1 Processing object type TRANSPORTABLE_EXPORT/INDEX/INDEX
09-JAN-24 15:24:11.479: W-1      Completed 16611 INDEX objects in 234 seconds
09-JAN-24 15:24:11.479: W-1 Processing object type TRANSPORTABLE_EXPORT/INDEX/FUNCTIONAL_INDEX/INDEX
[...]

This time all the tables were imported successfully. There were other issues though, but that might make up another blog post.

Oracle RMAN: connect / as sysbackup

This post is about RMAN connects and role separation. During an Oracle Database installtion, one can choose which OS group should map to which role for database administration. There is the well-known “dba” group which can do nearly everything to the database and maps to the SYSDBA role. And there are a few more groups for key management, dataguard administration and also for backup/restore. The backup/restore group is todays topic. A customer wants to perform the database backups by an OS user other than “oracle” which is the primary DBA user. That shouldn’t be a big thing, because we have these roles I mentioned. But wait, at first we need to check if the database software is configured for role separtion. In the file “$ORACLE_HOME/rdbms/lib/config.c” we can find this information.

[oracle@vm160 lib]$ grep -e "^\.L.*string" config.c
.Ldba_string:     .string "dba"
.Loper_string:    .string "oper"
.Lasm_string:     .string ""
.Lbkp_string:     .string "backupdba"
.Ldgd_string:     .string "dgdba"
.Lkmt_string:     .string "kmdba"
.Lrac_string:     .string "racdba"

In this case we are good to go. If we find the same OS group for the different roles, we need to recompile the software. The basic steps are described in MOS Note 558478.1 and are roughly these:

  • Stop everything that runs from this database home
  • make a backup of $ORACLE_HOME/rdbms/lib/config.o
  • change $ORACLE_HOME/rdbms/lib/config.c to map the roles to the desired OS groups
  • re-create config.o: make -f ins_rdbms.mk config.o
  • do a recompile/relink by running “make -f ins_rdbms.mk ioracle”
  • bring everything up again

Of cause the OS groups should be present at that time. Mine look like this:

[oracle@vm160 ~]$ tail -7 /etc/group
oinstall:x:54321:oracle
dba:x:54322:oracle
oper:x:54323:oracle
backupdba:x:54324:oracle
dgdba:x:54325:oracle
kmdba:x:54326:oracle
racdba:x:54330:oracle

What we now need is an OS user, that shall be used for performing database backups in the future. This user must be part of the “backupdba” group for being able to connect as SYSBACKUP to the database but must not be in the “dba” group because that would allow a connection as SYSDBA which we do not want. Beside that, the user must be part of the “oinstall” group which is the group that owns the database software installation.

[root@vm160 ~]# useradd -g oinstall-G backupdba,oinstall orarman
[root@vm160 ~]# id orarman
uid=54322(orarman) gid=54321(oinstall) groups=54321(oinstall),54324(backupdba)

I also defined a password for the new user, you can provide SSH keys as well to allow logins. So first I check with SQL*Plus if the user can connect to the database as SYSBACKUP but not as SYSDBA.

[orarman@vm160 ~]$ echo $ORACLE_SID
orcl19
[orarman@vm160 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Dec 21 07:25:07 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

ERROR:
ORA-01017: invalid username/password; logon denied


Enter user-name:
[orarman@vm160 ~]$ sqlplus / as sysbackup

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Dec 21 07:25:15 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.18.0.0.0

SQL>

That looks good so far and is exactly how I wanted it to behave. But in the end I need RMAN for doing all the backup things.

[orarman@vm160 ~]$ rman target /

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Dec 21 07:28:31 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00554: initialization of internal recovery manager package failed
RMAN-04005: error from target database:
ORA-01017: invalid username/password; logon denied

[orarman@vm160 ~]$ rman target / as sysbackup

Argument     Value          Description
-----------------------------------------------------------------------------
target       quoted-string  connect-string for target database
catalog      quoted-string  connect-string for recovery catalog
auxiliary    quoted-string  connect-string for auxiliary database
nocatalog    none           if specified, then no recovery catalog
cmdfile      quoted-string  name of input command file
log          quoted-string  name of output message log file
trace        quoted-string  name of output debugging message log file
append       none           if specified, log is opened in append mode
debug        optional-args  activate debugging
msgno        none           show RMAN-nnnn prefix for all messages
send         quoted-string  send a command to the media manager
pipe         string         building block for pipe names
script       string         name of catalog script to execute
using        list of args   arguments for rman variables
timeout      integer        number of seconds to wait for pipe input
checksyntax  none           check the command file for syntax errors
-----------------------------------------------------------------------------
Both single and double quotes (' or ") are accepted for a quoted-string.
Quotes are not required unless the string contains embedded white-space.

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00552: syntax error in command line arguments
RMAN-01009: syntax error: found "as": expecting one of: "append, auxiliary, catalog, checksyntax, cmdfile, log, msgno, nocatalog, pipe, script, send, target, timeout, using, @, ;"
RMAN-01007: at line 2 column 1 file: command line arguments

Ok, obviously not as straight-forward as I thought. Let’s check the docs how to connect from RMAN to a database: Oracle 19 Backup and Recovery Reference – connectStringSpec.

Reading the syntax diagram my connect strings are correct. Single quotes are optional and I can add “as sysbackup” to any connect identifier. Reading a bit further, there is this sentence:

If you do not specify a user ID or password when connecting to a target database, then a slash establishes a connection using the SYSDBA privilege by using operating system authentication (see Example 4-12).

Oracle 19 Backup and Recovery Reference: connectStringSpec

Does that mean, when I use the slash, it will always use the AS SYSDBA connection? I can’t believe this to be true. Let’s try RMAN without any connection and try to establish a connection at the RMAN prompt.

[orarman@vm160 ~]$ rman

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Dec 21 06:59:21 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

RMAN> connect target / as sysbackup

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-00558: error encountered while parsing input commands
RMAN-01009: syntax error: found "as": expecting one of: "newline, ;"
RMAN-01007: at line 1 column 18 file: standard input

The error message is somewhat helpful. RMAN finds the word “as” when it expects a newline or semicolon. So maybe RMAN expects the connect string as a single value so let’s try with quotes.

RMAN> connect target '/ as sysbackup'

connected to target database: ORCL19 (DBID=422142402)

RMAN>

Ok, great. This works. Let’s give it another try using double-quotes. Not in the syntax diagram but who knows?

[orarman@vm160 ~]$ rman

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Dec 21 06:59:41 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

RMAN> connect target "/ as sysbackup"

connected to target database: ORCL19 (DBID=422142402)

RMAN>

That works too. Good to know. According to the docs, only single-quotes are allowed and are optional. That is obviously not true for all cases. Now finally I want to invoke RMAN directly from the command line including the connect string. Maybe these quotes will help to make the “/ as sysdba” a single parameter value rather than three.

[orarman@vm160 ~]$ rman target "'/ as sysbackup'"

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Dec 21 07:47:41 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

connected to target database: ORCL19 (DBID=422142402)

RMAN>

This does the trick. And it doesn’t matter, if we enclose the single-quoted string in double-quotes or vice versa. We need two quotes because the outer quotes are interpreted by the shell and tell the shell, that anyting inside these quotes shall be treated as a single value. The inner string with the final connect string is then passed to RMAN which handles it as shown above.

Conclusion: It is possible to establish a RMAN connection using the SYSBACKUP privilege without using a password, e.g. using OS authentication. But it is not as straight-forward as I thought and the docs are not quite clear about the connection string.

RMAN recovery window over redundancy

One of my favorite topics around the Oracle Database is backup and recovery because it is so essential for successfully operating a database landscape. The topic today is a basic setting in RMAN, that controls the lifetime of backups, the “retention policy”. Let’s get into it.

When planning a backup strategy, there are two major values, that should be taken into account. One is the Recovery Time Objective (RTO) which is the amount of time spent to bring a system back to life after an incident. The second is the Recovery Point Objective (RPO) which represents the point in time to which a system shall be restored to get back to operation. The latter one is mainly influencing our retention policy. Imagine the following situation, a major application update takes place which does a lot of DDL and DML to the database because there are so many cool new features in the new release of that application. But for some reason the update fails when it was halfway through. Of cause it cannot be rolled back easily because of the DDL that took place. So the DBA is asked to restore the database to the time just before the update began.

That is, where the fun part starts. Basically it should be an easy thing using RMAN. Set the point in time, restore the controlfile, restore the database, roll it forward until the point in time and then open the database. So let’s go ahead.

[oracle@vm160 ~]$ rman target /

Recovery Manager: Release 19.0.0.0.0 - Production on Tue Nov 7 08:33:41 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

connected to target database: ORCL19 (DBID=422142402)

RMAN> startup nomount force

Oracle instance started

Total System Global Area    2147482320 bytes

Fixed Size                     9165520 bytes
Variable Size               1342177280 bytes
Database Buffers             788529152 bytes
Redo Buffers                   7610368 bytes

RMAN> set dbid=422142402

executing command: SET DBID

RMAN> run {
2> set until time "to_date('06.11.2023 04:00:00', 'dd.mm.yyyy hh24:mi:ss')";
3> restore controlfile from autobackup;
4> alter database mount;
5> restore database;
6> recover database;
7> }

executing command: SET until clause

Starting restore at 2023-11-07 08:35:08
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=237 device type=DISK

recovery area destination: /u01/app/oracle/fra
database name (or database unique name) used for search: ORCL19
channel ORA_DISK_1: no AUTOBACKUPS found in the recovery area
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231106
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231105
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231104
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231103
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231102
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231101
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231031
channel ORA_DISK_1: no AUTOBACKUP in 7 days found
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of restore command at 11/07/2023 08:35:11
RMAN-06172: no AUTOBACKUP found or specified handle is not a valid copy or piece

What? Why don’t I have any backup of my controlfile? Let’s check that.

RMAN> list backup of controlfile summary;


List of Backups
===============
Key     TY LV S Device Type Completion Time     #Pieces #Copies Compressed Tag
------- -- -- - ----------- ------------------- ------- ------- ---------- ---
68      B  F  A DISK        2023-11-07 08:44:01 1       1       NO         TAG20231107T084400

Ok, there is a controlfile backup, but just one. So what are my RMAN settings? Can I see something?

RMAN> show all;

using target database control file instead of recovery catalog
RMAN configuration parameters for database with db_unique_name ORCL19 are:
CONFIGURE RETENTION POLICY TO REDUNDANCY 1; # default
CONFIGURE BACKUP OPTIMIZATION OFF; # default
CONFIGURE DEFAULT DEVICE TYPE TO DISK; # default
CONFIGURE CONTROLFILE AUTOBACKUP ON; # default
CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '%F'; # default
CONFIGURE DEVICE TYPE DISK PARALLELISM 1 BACKUP TYPE TO BACKUPSET; # default
CONFIGURE DATAFILE BACKUP COPIES FOR DEVICE TYPE DISK TO 1; # default
CONFIGURE ARCHIVELOG BACKUP COPIES FOR DEVICE TYPE DISK TO 1; # default
CONFIGURE MAXSETSIZE TO UNLIMITED; # default
CONFIGURE ENCRYPTION FOR DATABASE OFF; # default
CONFIGURE ENCRYPTION ALGORITHM 'AES128'; # default
CONFIGURE COMPRESSION ALGORITHM 'BASIC' AS OF RELEASE 'DEFAULT' OPTIMIZE FOR LOAD TRUE ; # default
CONFIGURE RMAN OUTPUT TO KEEP FOR 7 DAYS; # default
CONFIGURE ARCHIVELOG DELETION POLICY TO NONE; # default
CONFIGURE SNAPSHOT CONTROLFILE NAME TO '/u01/app/oracle/product/db/19/dbs/snapcf_orcl19.f'; # default

The thing is the redundancy=1 as the retention policy. A redundancy of 1 (in words: one) means just what it says, RMAN keeps exactly one backup of each file it backs up. And often I see backup strategies relying highly on redundancy. Say I want to keep 2 weeks of backups for my database. A full backup takes place every sunday, so I set retention policy to “redundancy 2”. That way, RMAN keeps 2 full backups of my database which, according to the backup schedule, will be good for 2 weeks of backups. No, it is not. Why not? For a couple of reasons. First, as shown, the redundancy applies to all types of backups, also controlfile bakups etc. First, to be able to restore the database to the point 2 weeks ago, we’ll need a controlfile backup from that time. And there might be many of those backups. Second, if we change the backup schedule to backup the database twice a week, we change the retention to about one week implicitely. That’s not what I want it to be.

So how do I come around this? The answer is quite simple: Change the retention policy to be a recovery window rather than redundancy.

RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 14 DAYS;;

new RMAN configuration parameters:
CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 14 DAYS;
new RMAN configuration parameters are successfully stored

With that setting the previous example would have run without any errors. There are many backups of the controlfile:

RMAN> list backup of controlfile summary;


List of Backups
===============
Key     TY LV S Device Type Completion Time     #Pieces #Copies Compressed Tag
------- -- -- - ----------- ------------------- ------- ------- ---------- ---
17      B  F  A DISK        2023-02-24 13:54:24 1       1       NO         TAG20230224T135422
24      B  F  A DISK        2023-10-27 09:13:43 1       1       NO         TAG20231027T091343
26      B  F  A DISK        2023-10-27 09:16:32 1       1       NO         TAG20231027T091632
28      B  F  A DISK        2023-10-28 02:00:06 1       1       NO         TAG20231028T020006
35      B  F  A DISK        2023-10-29 01:00:47 1       1       NO         TAG20231029T010046
37      B  F  A DISK        2023-10-29 02:00:05 1       1       NO         TAG20231029T020005
39      B  F  A DISK        2023-10-30 02:00:08 1       1       NO         TAG20231030T020008
41      B  F  A DISK        2023-10-31 02:00:05 1       1       NO         TAG20231031T020005
43      B  F  A DISK        2023-11-01 02:00:07 1       1       NO         TAG20231101T020007
45      B  F  A DISK        2023-11-02 02:00:06 1       1       NO         TAG20231102T020006
46      B  F  A DISK        2023-11-02 13:28:47 1       1       NO         TAG20231102T132847
47      B  F  A DISK        2023-11-02 13:44:19 1       1       NO         TAG20231102T134419
48      B  F  A DISK        2023-11-02 13:47:53 1       1       NO         TAG20231102T134753
49      B  F  A DISK        2023-11-02 13:57:21 1       1       NO         TAG20231102T135721
52      B  F  A DISK        2023-11-03 02:00:08 1       1       NO         TAG20231103T020008
54      B  F  A DISK        2023-11-04 02:00:07 1       1       NO         TAG20231104T020006
61      B  F  A DISK        2023-11-05 01:00:37 1       1       NO         TAG20231105T010037
63      B  F  A DISK        2023-11-05 02:00:06 1       1       NO         TAG20231105T020006
65      B  F  A DISK        2023-11-06 02:00:05 1       1       NO         TAG20231106T020005
66      B  F  A DISK        2023-11-07 08:24:25 1       1       NO         TAG20231107T082424
67      B  F  A DISK        2023-11-07 02:00:06 1       1       NO         TAG20231107T020006
68      B  F  A DISK        2023-11-07 08:44:01 1       1       NO         TAG20231107T084400

With all these backups I can easily restore the database to the given point in time.

[oracle@vm160 ~]$ rman target /

Recovery Manager: Release 19.0.0.0.0 - Production on Tue Nov 7 08:35:59 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

connected to target database: ORCL19 (not mounted)

RMAN> set dbid=422142402

executing command: SET DBID

RMAN> run {
2> set until time "to_date('06.11.2023 04:00:00', 'dd.mm.yyyy hh24:mi:ss')";
3> restore controlfile from autobackup;
4> alter database mount;
5> restore database;
6> recover database;
7> }

executing command: SET until clause

Starting restore at 2023-11-07 08:36:51
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=20 device type=DISK

recovery area destination: /u01/app/oracle/fra
database name (or database unique name) used for search: ORCL19
channel ORA_DISK_1: AUTOBACKUP /u01/app/oracle/fra/ORCL19/autobackup/2023_11_06/o1_mf_s_1152151205_lnjgnooh_.bkp found in the recovery area
channel ORA_DISK_1: looking for AUTOBACKUP on day: 20231106
channel ORA_DISK_1: restoring control file from AUTOBACKUP /u01/app/oracle/fra/ORCL19/autobackup/2023_11_06/o1_mf_s_1152151205_lnjgnooh_.bkp
channel ORA_DISK_1: control file restore from AUTOBACKUP complete
output file name=/u01/app/oracle/oradata/ORCL19/controlfile/o1_mf_kq6lhlbv_.ctl
output file name=/u01/app/oracle/fra/ORCL19/controlfile/o1_mf_kq6lhlmd_.ctl
Finished restore at 2023-11-07 08:36:53

released channel: ORA_DISK_1
Statement processed

Starting restore at 2023-11-07 08:36:58
Starting implicit crosscheck backup at 2023-11-07 08:36:58
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=20 device type=DISK
Crosschecked 48 objects
Finished implicit crosscheck backup at 2023-11-07 08:36:59

Starting implicit crosscheck copy at 2023-11-07 08:36:59
using channel ORA_DISK_1
Finished implicit crosscheck copy at 2023-11-07 08:36:59

searching for all files in the recovery area
cataloging files...
cataloging done

List of Cataloged Files
=======================
File Name: /u01/app/oracle/fra/ORCL19/autobackup/2023_11_06/o1_mf_s_1152151205_lnjgnooh_.bkp
File Name: /u01/app/oracle/fra/ORCL19/autobackup/2023_11_07/o1_mf_s_1152260664_lnmsk92o_.bkp
File Name: /u01/app/oracle/fra/ORCL19/autobackup/2023_11_07/o1_mf_s_1152237606_lnm30pow_.bkp

using channel ORA_DISK_1

skipping datafile 5; already restored to file /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6m8z4c_.dbf
skipping datafile 6; already restored to file /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6m8z7g_.dbf
skipping datafile 8; already restored to file /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6m8zgv_.dbf
channel ORA_DISK_1: starting datafile backup set restore
channel ORA_DISK_1: specifying datafile(s) to restore from backup set
channel ORA_DISK_1: restoring datafile 00001 to /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6l8vf4_.dbf
channel ORA_DISK_1: restoring datafile 00003 to /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6lbyd0_.dbf
channel ORA_DISK_1: restoring datafile 00004 to /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6ld1qb_.dbf
channel ORA_DISK_1: restoring datafile 00007 to /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_users_kq6ld4xm_.dbf
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1s2am3om_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1s2am3om_1_1 tag=TAG20231105T010006
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:15
channel ORA_DISK_1: starting datafile backup set restore
channel ORA_DISK_1: specifying datafile(s) to restore from backup set
channel ORA_DISK_1: restoring datafile 00019 to /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/system_umbenannt.dbf
channel ORA_DISK_1: restoring datafile 00020 to /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kzgxy7n2_.dbf
channel ORA_DISK_1: restoring datafile 00021 to /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kzgxy7n4_.dbf
channel ORA_DISK_1: restoring datafile 00022 to /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_kzgxy7n5_.dbf
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1t2am3p6_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1t2am3p6_1_1 tag=TAG20231105T010006
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:07
channel ORA_DISK_1: starting datafile backup set restore
channel ORA_DISK_1: specifying datafile(s) to restore from backup set
channel ORA_DISK_1: restoring datafile 00015 to /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_system_ks5pktn8_.dbf
channel ORA_DISK_1: restoring datafile 00016 to /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_sysaux_ks5pktn9_.dbf
channel ORA_DISK_1: restoring datafile 00017 to /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_undotbs1_ks5pktnb_.dbf
channel ORA_DISK_1: restoring datafile 00023 to /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_users_ln761vy3_.dbf
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1u2am3pd_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231105_data_1u2am3pd_1_1 tag=TAG20231105T010006
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:07
Finished restore at 2023-11-07 08:37:29

Starting recover at 2023-11-07 08:37:29
using channel ORA_DISK_1

starting media recovery

archived log for thread 1 with sequence 7 is already on disk as file /u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_1_kq6lkxbr_.log
archived log for thread 1 with sequence 8 is already on disk as file /u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_2_kq6lk9px_.log
channel ORA_DISK_1: starting archived log restore to default destination
channel ORA_DISK_1: restoring archived log
archived log thread=1 sequence=4
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231105_arch_202am3pk_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231105_arch_202am3pk_1_1 tag=TAG20231105T010035
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:01
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_4_lnmt9trc_.arc thread=1 sequence=4
channel default: deleting archived log(s)
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_4_lnmt9trc_.arc RECID=268 STAMP=1152261450
channel ORA_DISK_1: starting archived log restore to default destination
channel ORA_DISK_1: restoring archived log
archived log thread=1 sequence=5
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231105_arch_222am794_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231105_arch_222am794_1_1 tag=TAG20231105T020004
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:01
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_5_lnmt9w16_.arc thread=1 sequence=5
channel default: deleting archived log(s)
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_5_lnmt9w16_.arc RECID=269 STAMP=1152261452
channel ORA_DISK_1: starting archived log restore to default destination
channel ORA_DISK_1: restoring archived log
archived log thread=1 sequence=6
channel ORA_DISK_1: reading from backup piece /u01/app/oracle/backup/orcl19/ORCL19_20231106_arch_242aorl4_1_1
channel ORA_DISK_1: piece handle=/u01/app/oracle/backup/orcl19/ORCL19_20231106_arch_242aorl4_1_1 tag=TAG20231106T020004
channel ORA_DISK_1: restored backup piece 1
channel ORA_DISK_1: restore complete, elapsed time: 00:00:01
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_6_lnmt9yvg_.arc thread=1 sequence=6
channel default: deleting archived log(s)
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_11_07/o1_mf_1_6_lnmt9yvg_.arc RECID=270 STAMP=1152261455
archived log file name=/u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_1_kq6lkxbr_.log thread=1 sequence=7
archived log file name=/u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_2_kq6lk9px_.log thread=1 sequence=8
media recovery complete, elapsed time: 00:00:07
Finished recover at 2023-11-07 08:37:42

Setting the RMAN retention policy to any redundancy is a really bad idea. It might work out for the backup of the datafiles, but it will definitely cause issues with the backups of controlfiles.

So typically we have a requirement for the RTO. We should always use the given value for setting the RMAN retention policy to a recovery windows so that RMAN can keep track of all the necessary backups and is able to keep them as long as they might be required.

Connecting to an Oracle Database – what could possible go wrong

This is gonna be a very short blog post, isn’t it? Connecting to an Oracle Database is straight forward. Add and alias to your tnsnames.ora file with the propper connection description, save it, start SQL*Plus (or SQLcl or whatever) and type

SQL> connect myuser/mysecret@mytnsalias

And here we go, we have a connection. Or even simpler, use the EZConnect syntax for creating a connection which makes the tnsnames.ora obsolete.

SQL> connect myuser/mysecret@db-hostname:1521/db-servicename

But you guess it, I wouldn’t be writing this if it is always as easy as this. Errors during the process of establishing a connection to an Oracle Database are propably the most common issues that I need to troubleshoot and solve.

For this post, I asume that a tnsnames.ora file is being used rather than EZConnect or a central LDAP directory. I’ll walk through the connection process step by step by using “SQL*Plus” and “tnsping”.

TNS-03505: Failed to resolve name / ORA-12154: TNS:could not resolve the connect identifier specified

[opc@cmp-openvpn ~]$ sqlplus system/********@db23c

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 06:27:09 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12154: TNS:could not resolve the connect identifier specified
[oracle@db23ctest ~]$ tnsping db23c

TNS Ping Utility for Linux: Version 23.0.0.0.0 - Production on 28-SEP-2023 06:29:45

Copyright (c) 1997, 2023, Oracle.  All rights reserved.

Used parameter files:
/u01/app/oracle/product/23.0.0.0/dbhome_1/network/admin/sqlnet.ora

TNS-03505: Failed to resolve name

These two error messages are basically the same. It means, that we were unable to find the given alias in the tnsnames.ora file. Sounds like a simple thing, but may have different root causes:

  • The alias is not in the TNSNAMES.ORA file
  • A wrong TNSNAMES.ORA file was being used, the path for the SQLNET.ORA file might indicate the location. I wrote about the search order in another blog post.
  • The parameter NAMES.DEFAULT_DOMAIN is specified in SQLNET.ORA. The value of this parameter is automatically added to the connection alias before searching the TNSNAMES.ORA in case it contains no dots.

TNS-12545 / ORA-12545: Connect failed because target host or object does not exist

[opc@cmp-openvpn ~]$ sqlplus system/********@db23ctest

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 06:43:28 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12545: Connect failed because target host or object does not exist

This error means, that the given hostname in the connect description could not be resolved by DNS. So we need to verify the hostname using “nslookup” or “ping” for instance.

TNS-12535: TNS:operation timed out / ORA-12170: TNS:Connect timeout occurred

[opc@cmp-openvpn ~]$ sqlplus system/********@db23ctest

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 06:56:23 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12170: TNS:Connect timeout occurred
C:\Users\marco\>tnsping db23test

TNS Ping Utility for 64-bit Windows: Version 19.0.0.0.0 - Production on 28-SEP-2023 08:54:44

Copyright (c) 1997, 2021, Oracle.  All rights reserved.

Used parameter files:
C:\oracle\client19\network\admin\sqlnet.ora


Used TNSNAMES adapter to resolve the alias
Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.30.110)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = DB23C_db23test.snrsbctl.vcnrsbctl.oraclevcn.com)))
TNS-12535: TNS:operation timed out

When encountering this error, we were able to resolve the alias via TNSNAMES.ORA and were also able to identify the database hostname. But for some reason we were not able to connect to the listener port. Most propably this is due to a firewall rule preventing the connection.

ORA-12541: TNS:no listener

[opc@cmp-openvpn ~]$ sqlplus system/********@db23ctest

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 07:02:13 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12541: TNS:no listener
[oracle@db23ctest ~]$ tnsping db23test

TNS Ping Utility for Linux: Version 23.0.0.0.0 - Production on 28-SEP-2023 07:02:42

Copyright (c) 1997, 2023, Oracle.  All rights reserved.

Used parameter files:
/u01/app/oracle/product/23.0.0.0/dbhome_1/network/admin/sqlnet.ora


Used TNSNAMES adapter to resolve the alias
Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.30.110)(PORT = 1522)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = DB23C_db23test.snrsbctl.vcnrsbctl.oraclevcn.com)))
TNS-12541: Cannot connect. No listener at host 10.0.30.110 port 1522.
 TNS-12560: Database communication protocol error.
  TNS-00511: No listener
   Linux Error: 111: Connection refused

This error seems to be straight forward, but may occur for several reasons. At least we made it to the database server. Possible reasons are:

  • The listener is not running
  • The listener is running but listens to another port
  • The listener is running and listens to the given port, but is not registered with the specified IP address

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

[opc@cmp-openvpn ~]$ sqlplus system/********@db23ctest

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 07:15:34 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-12514: TNS:listener does not currently know of service requested in connect
descriptor

At this point we made it to the listener, but as the error messages states, the listener does not know the service name we are trying to connect to. This error can only be seen using SQL*Plus (or similar), TNSPING will not reveal any issues at this point since it only checks the connection until it gets a response from the server at the given port. It does not verify the service name. The service name comes into play, when really we want to establish a database connection.

[oracle@db23ctest ~]$ tnsping db23test

TNS Ping Utility for Linux: Version 23.0.0.0.0 - Production on 28-SEP-2023 07:17:39

Copyright (c) 1997, 2023, Oracle.  All rights reserved.

Used parameter files:
/u01/app/oracle/product/23.0.0.0/dbhome_1/network/admin/sqlnet.ora


Used TNSNAMES adapter to resolve the alias
Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.30.110)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = DB23C_db23test.snrsbctl.vcnrsbctl.oraclevcn)))
OK (0 msec)

Again there are several possible reasons for encountering an ORA-12514:

  • We are using a wrong service name.
  • The service is not registered with the listener. Maybe the database is down or the service is not started.
  • The databases “listener*” parameters are not properly configured preventing the database from registering its services with the listener.

ORA-01017: invalid credential or not authorized; logon denied

[opc@cmp-openvpn ~]$ sqlplus system/Wr0ngPwd@db23ctest

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 28 07:30:15 2023
Version 19.19.0.0.0

Copyright (c) 1982, 2023, Oracle.  All rights reserved.

ERROR:
ORA-01017: invalid credential or not authorized; logon denied

If we see this error message, we finally made it to the database. We reached the listener, the listener did know of our requested service name and handed the connection over to the database. The database now checks the logon credentials and returns this error in case this validation failed.

Summary

Establishing a connection to an Oracle Database is quite a complex thing. And it starts the be a “real” database issue when we encounter an ORA-1017 error. Anything else happens before even touching the database.

Suspicous ORA-12516 by Oracle SCAN Listener

A couple of days ago, I had to move some databases to new hardware for one of my customers. Don’t ask about the Oracle Database version we came from… The new systems are two 2-node clusters connected using some standby machanism. Due to the lack of a RAC license, the cluster is working as a failover cluster. Of cause we use Oracle Grid Infrastructure to achieve that.

$ opatch lspatches
34863894;TOMCAT RELEASE UPDATE 19.0.0.0.0 (34863894)
34768569;ACFS RELEASE UPDATE 19.18.0.0.0 (34768569)
34768559;OCW RELEASE UPDATE 19.18.0.0.0 (34768559)
34765931;DATABASE RELEASE UPDATE : 19.18.0.0.230117 (REL-JAN230131) (34765931)
33575402;DBWLM RELEASE UPDATE 19.0.0.0.0 (33575402)

When the surrounding applications came up again, we began to experience strange errors when applications tried to connect to the database. They got ORA-12516 and ORA-12520 errors. Most propably we run out of processes inside the database instance causing the listener to block new connections. MOS note “Intermittent TNS-12520 or TNS-12519 or TNS-12516 Connecting via Oracle Net Listener (Doc ID 240710.1)” describes that quite well. I checked the resource

SQL:>select * from v$resource_limit where resource_name in ('processes', 'sessions');

   RESOURCE_NAME    CURRENT_UTILIZATION    MAX_UTILIZATION    INITIAL_ALLOCATION    LIMIT_VALUE    CON_ID
________________ ______________________ __________________ _____________________ ______________ _________
processes                           170                209       1000                  4000             0
sessions                            184                210       1540                  6048             0

Obviously the processes limit is not the issue, there are plenty of processes left to handle new sessions. So I had a look into to SCAN listener log and found those errors:

17-JUL-2023 08:26:53 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53249)) * establish * xdb03_svc * 12516
TNS-12516: TNS:listener could not find available handler with matching protocol stack

Before that, I found hundreds of entries indicating an established connection, here are some of these:

17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53059)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53060)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53061)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53062)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53063)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53064)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53065)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53066)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53067)) * establish * xdb03_svc * 0
17-JUL-2023 08:26:46 * (CONNECT_DATA=(SERVICE_NAME=xdb03_svc)(CID=(PROGRAM=quvae.exe)(HOST=CLIENT-W004)(USER=someuser))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.101.85.4)(PORT=53068)) * establish * xdb03_svc * 0

In a single minute there were more than 1500 established connections:

$ grep "17-JUL-2023 08:26" /u01/app/oracle/diag/tnslsnr/oracle-srv2/listener_scan1/trace/listener_scan1.log | wc -l
1504

Using a SCAN listener, the connection process is as follows:

  1. Client calls the SCAN listener and asks for a service to connect to
  2. SCAN listener returns the local listener that hosts the given service to the client
  3. Client calls the local listener and asks for a connection to the service
  4. Local listener forks a database process and hands over the TCP connection

After that, the listeners are no longer involved in the communication. So the next step was to check the log of the local listener. None of these connections reported by the SCAN listener showed up in the local listener.

The client never made it to the local listener, no server process was spawned and no datatabase session was created. Why is the listener blocking then? The MOS note has an explanation for that too:

The listener counts the number of connections it has established to the instance
but does not immediately get information about connections that have terminated.
Only when PMON/LREG updates the listener via SERVICE_UPDATE is the listener
informed of current load. Since this can take as long as 10 minutes, there can be
a difference between the current instance load according to the listener
and the actual instance load.

When the listener believes the current number of connections has reached maximum
load, it may set the state of the service handler for an instance to “blocked”
and begin refusing incoming client connections with either of the following
errors:

Given that information, it is crystal-clear why the SCAN listener started to block new connections. It counted up to a thousand connections which is the limit of the database. At this point it started to block even though none of these connections made it to the database.

But why not? In the SCAN listeners log we can see, that a connection attempt follows the previous attempt without any delay. In contrast to this, the TNSNAMES.ORA alias of cause has some timeouts and delays defined according to MAA whitepapers (here and here):

XDB03 =
  (DESCRIPTION = 
    (CONNECT_TIMEOUT= 90)(RETRY_COUNT=50)(RETRY_DELAY=3)(TRANSPORT_CONNECT_TIMEOUT=3) 
    (ADDRESS_LIST = 
      (ADDRESS = (PROTOCOL = TCP)(HOST=oracle-scan1)(PORT=1521))
    ) 
    (ADDRESS_LIST = 
      (ADDRESS = (PROTOCOL = TCP)(HOST=oracle-scan2)(PORT=1521))
    ) 
    (CONNECT_DATA=
      (SERVICE_NAME = xdb03_svc)
    )
  )

Obviously these parameters are not in effect. Otherwise there should be delays between two connection attempts. Also, if the attempts would really fail, they should stop after 50 retries.

The customer does not really know, what interface the application is using for database connections. It is “some native driver which does not pick up tnsnames.ora”, whatever that means. As soon as they switched to an installed Oracle Client, those strange connection storms disappeared. Obviously the application is now using the retry and timeout parameters as it should be.

We tried to capture network packets from the clients in questions, but that put to much load on the client causing it to slow down to much. If you have any ideas, how such a situation can be further debugged, I would be happy to get that feedback.

Oracle ASM – why user defined filenames should be avoided

Last week my night was interrupted by a customer call. He told me, that on one of his systems an ASM diskgroup ran out of space. All the instances are stuck and so on, you know those stories. Luckily, he already found two files that were consuming some terrabytes which he considered as obsolete. I’ll tell you in a minute, why he was assuming that. This is an Exadata system running several databases, they use bigfile tablespaces, just to mention that. But unfortunately he was not able to delete these two files and that’s why he wanted me to get rid of these files.

So I logged in to his system and had a look for myself. From what the customer told me, the files seemed to be not in use anymore. At least when reading the directory name.

ASMCMD> cd +DATAC1/ASM/DATAFILE_OLD
ASMCMD> pwd
+DATAC1/ASM/DATAFILE_OLD
ASMCMD> ls -lsh
Type      Redund  Striped  Time             Sys  Block_Size     Blocks          Bytes           Space  Name
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K  857110785           6.4T           19.2T  EXCON_DATEN_TS.1157.1127667053
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K  475892737           3.5T           10.6T  t_bundle_uses_tbs.1574.1110647865

Because the directory was named “DATAFILE_OLD”, these two files were probably some orphaned files. So I gave it another try to get rid of them:

ASMCMD> rm -rf EXCON_DATEN_TS.1157.1127667053
ORA-15032: not all alterations performed
ORA-15028: ASM file '+DATAC1/ASM/DATAFILE_OLD/EXCON_DATEN_TS.1157.1127667053' not dropped; currently being accessed (DBD ERROR: OCIStmtExecute)

But I experienced the same error that the customer got beforehand. That is where the story really starts. The reason for not being able to delete the files is quite obvious, the files are still in use by some instance. Fortunately ASM takes care of that and prevents us from deleting files, that are currently in use. But the question is, to which instance/database these files belong to? ASM has a “lsof” command similar to the “lsof” in Linux that shows a list of instances and files that are currently in use. So let’s start with that.

[grid@some-server ~]$ asmcmd lsof -G DATAC1 | grep -i datafile_old
[grid@some-server ~]$ asmcmd lsof -G DATAC1 | grep -i excon
APLOUAT_  APLOUAT1       +DATAC1/APLOUAT_FRA1XP/DATAFILE/local_excon_index_ts.680.1072824757
APLOUAT_  APLOUAT1       +DATAC1/APLOUAT_fra1xp/DATAFILE/excon_daten_ts
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/excon_daten_ts.323.1136681069
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/excon_index_ts.1137.1136679463
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/local_excon_data_ts.1139.1136679463
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/local_excon_index_ts.1160.1136679461

Ok, no datafiles containing the strange “DATAFILE_OLD” and some files containing at least parts of the filename in question, but not exactly what I was looking for.

After some time of thinking about this, I had an idea. It could be related to aliases. If you create a datafile from within a database inside ASM and specify a filename, you will get two things: A datafile in Oracle Managed File format and an alias with in the given directory and the given filename pointing to that datafile. ASM obviously knows, that the file is in use, but since the database is opening the file using the user defined filename, “lsof” will show the name of the alias rather than the real filename.

How can I find out, to which database the files belong? There are views like V$ASM_ALIAS and V$ASM_FILE, but they are not easy to query. Some searching lead me to this very helpful thread in the Oracle Forums. The query was exactly what I needed. I simply had to adapt the diskgroup number and then walk through all the databases to find the aliases pointing to the two files in question. Here’s the query again:

WITH qry1 AS
(
SELECT aa.name,
       ag.name group_name,
       af.type,
       aa.file_number,
       aa.file_incarnation,
       aa.parent_index parent_index
FROM v$asm_alias aa,
     v$asm_diskgroup ag,
     v$asm_file af
WHERE aa.file_number = af.file_number
AND aa.group_number = ag.group_number
AND ag.group_number = af.group_number
AND aa.file_incarnation = af.incarnation
AND aa.system_created = 'N'
AND ag.group_number = &&groupnumber.
)
SELECT alias_name,
       '+'||group_name||'/'||LISTAGG(CASE WHEN alias_name = root_name THEN name END,'/') WITHIN GROUP(ORDER BY lvl DESC) alias_path,
       '+'||group_name||'/'||LISTAGG(CASE WHEN alias_name != root_name THEN name END,'/') WITHIN GROUP(ORDER BY lvl DESC) file_path
FROM
(
SELECT aa.name,
       q1.group_name,
       q1.file_number,
       q1.name alias_name,
       LEVEL lvl,
       CONNECT_BY_ROOT aa.name root_name
FROM v$asm_alias aa,
     qry1 q1
WHERE aa.group_number = &&groupnumber.
START WITH (aa.name = q1.name OR (aa.name != q1.name AND aa.file_number = q1.file_number))
CONNECT BY PRIOR aa.parent_index = aa.reference_index
AND q1.name = PRIOR q1.name
)
GROUP BY group_name,file_number,alias_name;

I found the aliases in database APLOUAT. Let’s check the ASM directory:

ASMCMD> cd DATAC1/APLOUAT_fra1xp/DATAFILE/
ASMCMD> ls -lsh
Type      Redund  Striped  Time             Sys  Block_Size     Blocks          Bytes           Space  Name
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K       9185          71.8M            216M  CDC_METADATA_TS.667.1072824731
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  N            8K  857110785           6.4T           19.2T  EXCON_DATEN_TS => +DATAC1/ASM/DATAFILE_OLD/EXCON_DATEN_TS.1157.1127667053
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   25586305         195.2G          585.6G  IIMEX_BRIDGEHEAD_TS.295.1072823641
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   80536705         614.4G            1.8T  IMEX_TS.302.1072792101
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K  603979777           4.5T           13.5T  LICENSING_SMALL_TS.306.1072792099
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   94317569         719.6G            2.1T  LION_STAGE_DATA_TS.298.1072792101
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K     262145             2G              6G  LOCAL_EXCON_INDEX_TS.680.1072824757
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   10838145          82.7G          248.1G  PROCESS_INDEX_TS.315.1072823415
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   15859713           121G            363G  UNDOTS1.676.1132578301
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  Y            8K   19660801           150G            450G  UNDOTS2.2364.1132578301
DATAFILE  HIGH    COARSE   MAY 29 20:00:00  N            8K  475892737           3.5T           10.6T  t_bundle_uses_tbs => +DATAC1/ASM/DATAFILE_OLD/t_bundle_uses_tbs.1574.1110647865

At that point, I was able to tell the customer, that these two files belong to tablespaces in a running database which is the reason, that these files cannot be easily deleted. The customer story ends here, but I have something more to tell. There might be an easier way to find the alias. Remember the output of “asmcmd lsof”:

[grid@some-server ~]$ asmcmd lsof -G DATAC1 | grep -i excon
APLOUAT_  APLOUAT1       +DATAC1/APLOUAT_FRA1XP/DATAFILE/local_excon_index_ts.680.1072824757
APLOUAT_  APLOUAT1       +DATAC1/APLOUAT_fra1xp/DATAFILE/excon_daten_ts
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/excon_daten_ts.323.1136681069
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/excon_index_ts.1137.1136679463
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/local_excon_data_ts.1139.1136679463
LIONCDB_  LIONCDB1       +DATAC1/LIONCDB_DB/FB877908850202CFE0538C200A0A1A63/DATAFILE/local_excon_index_ts.1160.1136679461

See the highlighted line, this is one of the two aliases pointing to the files in question. Since the names are somehow similar, doing a case-insensitive search for open files containing parts of the name can reveal the aliases, that are pointing to these files. But this only works, when the user defined filename contains the tablespace name. If the filename is completely different, then you have to fall back to the query above.

At the end, this is a good example, why one should not use user defined filenames when using ASM. Better stick to Oracle Manged Filenames (OMF), this will make things much easier.

The search order for TNSNAMES.ORA

When reading the headline, you might think the answer is more or less trivial. Or at least it is stated in the docs. But in fact, it isn’t. Let’s have a quick glance at the docs. The “19c Net Services Administrator’s Guide” states the following:

1. The directory specified by the TNS_ADMIN environment variable.
2. If the TNS_ADMIN environment variable is not set or the file is not found in the TNS_ADMIN directory, then Oracle Net checks for the file in the ORACLE_HOME/network/admin directory. For a read-only Oracle home, Oracle Net checks the ORACLE_BASE_HOME/network/admin directory.

So let’s build a testcase to verify that. I created a CDB that hosts a PDB for each type directory, that may hold a tnsnames.ora file. As far as I know, it can reside in the current directory. in a directory pointed to by TNS_ADMIN and in ORACLE_HOME/network/admin. The TNS_ADMIN can be defined as a environment variable or, on Windows, inside the Registry.

SQL> select name from v$database;

NAME
---------
MPMCDB1

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDBA                           READ WRITE NO
         4 TNSLOCAL                       READ WRITE NO
         5 TNSREGISTRY                    READ WRITE NO
         6 TNSADMIN                       READ WRITE NO
         7 TNSORAHOME                     READ WRITE NO

For the test, I will create a tnsnames.ora in each of the mentioned locations with one alias in it. The only difference will be the service, the alias refers to.

Let’s check these files and settings first.

C:\Users\marco.mischke>type tnsnames.ora
TNSTEST=
 (DESCRIPTION =
   (ADDRESS = (PROTOCOL = TCP)(HOST = odax5ha0.support.robotron.de)(PORT = 1521))
   (CONNECT_DATA = (SERVICE_NAME = tnslocal.support.robotron.de))
 )


C:\Users\marco.mischke>reg query HKLM\SOFTWARE\Oracle\KEY_OraClient19Home1 /v TNS_ADMIN

HKEY_LOCAL_MACHINE\SOFTWARE\Oracle\KEY_OraClient19Home1
    TNS_ADMIN    REG_SZ    C:\TEMP
    
C:\Users\marco.mischke>type c:\Temp\tnsnames.ora
TNSTEST=
 (DESCRIPTION =
   (ADDRESS = (PROTOCOL = TCP)(HOST = odax5ha0.support.robotron.de)(PORT = 1521))
   (CONNECT_DATA = (SERVICE_NAME = tnsregistry.support.robotron.de))
 )

C:\Users\marco.mischke>set TNS_ADMIN=c:\Users\marco.mischke\TNSADMIN

C:\Users\marco.mischke>type c:\Users\marco.mischke\TNSADMIN\tnsnames.ora
TNSTEST=
 (DESCRIPTION =
   (ADDRESS = (PROTOCOL = TCP)(HOST = odax5ha0.support.robotron.de)(PORT = 1521))
   (CONNECT_DATA = (SERVICE_NAME = tnsadmin.support.robotron.de))
 )

C:\Users\marco.mischke>set TNS
TNS_ADMIN=c:\Users\marco.mischke\TNSADMIN

C:\Users\marco.mischke>type C:\oracle\client19\network\admin\tnsnames.ora
TNSTEST=
 (DESCRIPTION =
   (ADDRESS = (PROTOCOL = TCP)(HOST = odax5ha0.support.robotron.de)(PORT = 1521))
   (CONNECT_DATA = (SERVICE_NAME = tnsorahome.support.robotron.de))
 )

Time for the first test. I simply start SQL*Plus using the one alias defined in all four tnsnames.ora files.

C:\Users\marco.mischke>sqlplus pdbadmin/********@tnstest

SQL*Plus: Release 19.0.0.0.0 - Production on Wed May 10 10:47:05 2023
Version 19.15.0.0.0

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

Last Successful login time: Wed May 10 2023 09:26:24 +02:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> show con_name

CON_NAME
------------------------------
TNSLOCAL

Obviously my connection gets routed to the “TNSLOCAL” PDB which means, the tnsnames.ora in the current working directory was picked up.

Now I remove this file and try another connection.

C:\Users\marco.mischke>del tnsnames.ora

C:\Users\marco.mischke>sqlplus pdbadmin/********@tnstest

SQL*Plus: Release 19.0.0.0.0 - Production on Wed May 10 10:48:34 2023
Version 19.15.0.0.0

Copyright (c) 1982, 2021, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> show con_name

CON_NAME
------------------------------
TNSADMIN

Ok, now the tnsnames.ora from the directory pointed to by the TNS_ADMIN environment variable was used, because I am now connected to the “TNSADMI” PDB.

Next step is to unset TNS_ADMIN or, as an alternative, remove that tnsnames.ora file too and try again.

C:\Users\marco.mischke>set TNS_ADMIN=

C:\Users\marco.mischke>set TNS
Die Umgebungsvariable "TNS" ist nicht definiert.

C:\Users\marco.mischke>sqlplus pdbadmin/********@tnstest

SQL*Plus: Release 19.0.0.0.0 - Production on Wed May 10 10:49:32 2023
Version 19.15.0.0.0

Copyright (c) 1982, 2021, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> show con_name

CON_NAME
------------------------------
TNSREGISTRY

Last but not least, I remove the Registry key for TNS_ADMIN.

C:\Users\marco.mischke>reg delete HKLM\SOFTWARE\Oracle\KEY_OraClient19Home1 /v TNS_ADMIN
Registrierungswert TNS_ADMIN löschen (Ja/Nein)? Ja

C:\Users\marco.mischke>reg query HKLM\SOFTWARE\Oracle\KEY_OraClient19Home1 /v TNS_ADMIN


FEHLER: Der angegebene Registrierungsschlüssel bzw. Wert wurde nicht gefunden.

C:\Users\marco.mischke>sqlplus pdbadmin/********@tnstest

SQL*Plus: Release 19.0.0.0.0 - Production on Wed May 10 10:51:55 2023
Version 19.15.0.0.0

Copyright (c) 1982, 2021, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> show con_name

CON_NAME
------------------------------
TNSORAHOME

So finally I ended up in the “TNSORAHOME” PDB meaning, SQL*plus used the tnsnames.ora in the ORACLE_HOME.

These four tests reveal the search order for the SQL*Net configuration files, especially tnsnames.ora:

  1. Current working directory
  2. Directory pointed to by TNS_ADMIN environment variable
  3. Directory pointed to by TNS_ADMIN registry value
  4. ORACLE_HOME/network/admin

Keep that in mind, esspecially the first point. If your connection fails or ends up in a database that you do not expect, there might be a tnsnames.ora file in your current directory which gets picked up first regardless of any other settings.

Restore a PDB into another CDB

In my previous post I already wrote about a special restore scenario with the Multitenant Architecture. Today the story continues with another scenario that sounds quite simple in the first place. I will be using Oracle Database Version 19.18 for this topic.

So let’s get started. The goal is to restore a given PDB into another CDB at a specific point in time. Imagine some accidental data change happend which should be rolled back. Since only a small and rarely used table is affected, flashback or a complete Point-in-time Restore is not an option. The goal is to restore the PDB into another CDB at the given point in time to analyze the issue further. So let’s get started.

Instantiating a PDB in another CDB sounds like cloning at a first glance. But cloning always uses the running PDB as a source, there is no option to specify a point in time. So let’s try RMAN PDB duplication.

[oracle@odax5ha0 ~]$ rman

Recovery Manager: Release 19.0.0.0.0 - Production on Tue Mar 21 10:08:34 2023
Version 19.16.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

RMAN> connect target sys/********@odax5ha0:1521/mpmcdb2x5.support.robotron.de

connected to target database: MPMCDB2 (DBID=889235828)

RMAN> connect auxiliary sys/********@odax5ha0:1521/mpmcdb1x5.support.robotron.de

connected to auxiliary database: MPMCDB1 (DBID=1547213674)

RMAN> duplicate pluggable database pdba as pdbclone  until time "to_date('21.03.2023 09:10:00')";

Starting Duplicate PDB at 2023-03-21 10:10:28
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of Duplicate PDB command at 03/21/2023 10:10:28
RMAN-05501: aborting duplication of target database
RMAN-05649: duplicate pluggable database does not allow the use of UNTIL SCN or TO RESTORE POINT clause

Oh yes, good start. And RMAN tells the truth, the docs tell us that.

As in the preivous post, I need an temporary auxiliary CDB instance to restore my PDB. Good news, this time the steps will be a little easier. I start with a Pfile derived from the target CDB.

SQL> create pfile='/tmp/initaux.ora' from spfile;

File created.

I remove the “control_files” parameter and set the “db_unique_name” to something unique. Having that I can startup my auxiliary instance and create a SPfile for it.

[oracle@odax5ha0 ~]$ export ORACLE_SID=CDBDUP

[oracle@odax5ha0 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Mar 21 10:14:22 2023
Version 19.16.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Connected to an idle instance.

SQL> startup nomount pfile=/tmp/initaux.ora
ORACLE instance started.

Total System Global Area 4294963760 bytes
Fixed Size                  9142832 bytes
Variable Size             805306368 bytes
Database Buffers         3456106496 bytes
Redo Buffers               24408064 bytes

SQL> create spfile from pfile='/tmp/initaux.ora';

File created.

SQL> startup nomount force
ORACLE instance started.

Total System Global Area 4287959600 bytes
Fixed Size                  9142832 bytes
Variable Size             838860800 bytes
Database Buffers         3422552064 bytes
Redo Buffers               17403904 bytes

SQL>

Now i can go ahead and try the duplication again, this time at CDB level because this allows me to specify a point in time.

[oracle@odax5ha0 ~]$ rman

Recovery Manager: Release 19.0.0.0.0 - Production on Tue Mar 21 10:28:43 2023
Version 19.16.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

RMAN> connect target sys/********@odax5ha0:1521/mpmcdb1x5.support.robotron.de

connected to target database: MPMCDB1 (DBID=1547213674)

RMAN> connect auxiliary /

connected to auxiliary database (not started)

RMAN> duplicate database to CDBDUP pluggable database pdba until time "to_date('21.03.2023 09:10:00', 'dd.mm.yyyy hh24:mi:ss')";

Starting Duplicate Db at 2023-03-21 10:29:44
using target database control file instead of recovery catalog
allocated channel: ORA_AUX_DISK_1
channel ORA_AUX_DISK_1: SID=26 device type=DISK
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of Duplicate Db command at 03/21/2023 10:29:48
RMAN-05501: aborting duplication of target database
RMAN-06617: UNTIL TIME (2023-03-21 09:10:00) is ahead of last NEXT TIME in archived logs (2023-03-21 09:00:09)

Oh, look, another error. This is because there was not much DML happening and no log switch occured between the given point in time and right now. But this is easy to work around.

RMAN> alter system archive log current;

Statement processed

RMAN> duplicate database to CDBDUP pluggable database pdba until time "to_date('21.03.2023 09:10:00', 'dd.mm.yyyy hh24:mi:ss')";

Starting Duplicate Db at 2023-03-21 10:30:25
using channel ORA_AUX_DISK_1
duplicating Online logs to Oracle Managed File (OMF) location
duplicating Datafiles to Oracle Managed File (OMF) location

[...]

sql statement: alter pluggable database all open
Dropping offline and skipped tablespaces
Executing: alter database default tablespace system
Executing: drop tablespace "USERS" including contents cascade constraints
Finished Duplicate Db at 2023-03-21 10:34:50

RMAN>

The rest of the work is to unplug the PDB from the temporary auxiliary PDB, drop the auxiliary CDB and finally plug the PDB into the target CDB.

[oracle@odax5ha0 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Mar 21 10:41:30 2023
Version 19.16.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> select name from v$database;

NAME
---------
CDBDUP

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDBA                           READ WRITE NO
SQL> alter pluggable database pdba close immediate;

Pluggable database altered.

SQL> alter pluggable database pdba unplug into '/tmp/pdba.xml';

Pluggable database altered.

SQL> drop pluggable database pdba keep datafiles;

Pluggable database dropped.

SQL> startup mount restrict exclusive force
ORACLE instance started.

Total System Global Area 4287959600 bytes
Fixed Size                  9142832 bytes
Variable Size             838860800 bytes
Database Buffers         3422552064 bytes
Redo Buffers               17403904 bytes
Database mounted.
SQL> drop database;

Database dropped.

Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

[oracle@odax5ha0 ~]$ export ORACLE_SID=MPMCDB2
[oracle@odax5ha0 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Mar 21 10:44:12 2023
Version 19.16.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.16.0.0.0

SQL> select name from v$database;

NAME
---------
MPMCDB2

SQL> create pluggable database PDBA using '/tmp/pdba.xml' copy tempfile reuse;

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDBB                           READ WRITE NO
SQL> create pluggable database PDBA using '/tmp/pdba.xml' copy;

Pluggable database created.

SQL> alter pluggable database pdba open;

Pluggable database altered.

SQL> alter pluggable database pdba save state;

Pluggable database altered.

SQL> show pdbs

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

SQL>

And that’s just it. Easier than the other situation but not quite straight forward anyway. I think, this mght help in some situation.

Restore a dropped Pluggable Database

When reading the headline, the todays topic sounds not very complicated or special at all. But be warned, it definitely is. During a Migration-to-Multitenant project at a customers site we walked through quite a couple of restore scenarios. The most obvious scenario is dropping a PDB by accident. Of cause we have all the backups in place, that we will need to restore and recover the lost PDB.

Let’s start with what I did to the PDB:

[oracle@vm160 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Feb 23 13:54:28 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.18.0.0.0

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB1                           READ WRITE NO
         4 ORCL19PDB3                     READ WRITE NO
         5 ORCL19PDB2                     READ WRITE NO
SQL> alter session set container=pdb1;

Session altered.

SQL> select count(*) from hr.employees;

  COUNT(*)
----------
       108

SQL> alter session set container=cdb$root;

Session altered.

SQL> alter pluggable database pdb1 close immediate;

Pluggable database altered.

SQL> drop pluggable database pdb1 including datafiles;

Pluggable database dropped.

That was the easiest part of the story. You see, there were 3 PDBs, the dropped PDB had a table “EMPLOYEES” with 108 records. Now the simple goal ist to restore that PDB to right before the drop. To get that timestamp, we simply look it up in the alert.log:

[oracle@vm160 ~]$ tail /u01/app/oracle/diag/rdbms/orcl19/orcl19/trace/alert_orcl19.log
2023-02-23T13:55:01.355396+01:00
alter pluggable database pdb1 close immediate
2023-02-23T13:55:01.357622+01:00
PDB1(3):Pluggable database PDB1 closing
PDB1(3):JIT: pid 9707 requesting stop
PDB1(3):Closing sequence subsystem (3457133901505).
PDB1(3):Buffer Cache flush started: 3
2023-02-23T13:55:02.353510+01:00
PDB1(3):Buffer Cache flush finished: 3
Pluggable database PDB1 closed
Completed: alter pluggable database pdb1 close immediate
2023-02-23T13:55:14.816955+01:00
drop pluggable database pdb1 including datafiles
2023-02-23T13:55:17.338832+01:00
Deleted Oracle managed file /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_ksfwjspy_.dbf
Deleted Oracle managed file /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_temp_kq6nz8kv_.dbf
Deleted Oracle managed file /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kq6nz8kv_.dbf
Deleted Oracle managed file /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kq6nz8kt_.dbf
Deleted Oracle managed file /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_system_kq6nz8kh_.dbf
2023-02-23T13:55:17.616908+01:00
Stopped service pdb1
Completed: drop pluggable database pdb1 including datafiles

Now that we identified the propper timestamp which is roughly 13:55, let’s start with RMAN:

[oracle@vm160 ~]$ rman target /

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Feb 23 13:57:26 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

connected to target database: ORCL19 (DBID=422142402)

RMAN> run {
2> set until time "to_date('23.02.2023 13:55:00', 'dd.mm.yyyy hh24:mi:ss')";
3> restore pluggable database pdb1;
4> recover pluggable database pdb1;
5> }

executing command: SET until clause

Starting restore at 2023-02-23 13:58:23
using target database control file instead of recovery catalog
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of restore command at 02/23/2023 13:58:24
ORA-01403: no data found

RMAN> restore pluggable database pdb1;

Starting restore at 2023-02-23 13:58:31
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=277 device type=DISK
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of restore command at 02/23/2023 13:58:31
RMAN-06813: could not translate pluggable database PDB1

Ok, neither can we do a point-in-time restore nor can we do a complete and current restore of the dropped PDB. This is because the controlfile is our single point of truth and it simply forgot everything about the PDB when I dropped it.

What we need to restore the PDB is an auxiliary instance to restore and recover the lost PDB. MOS Note “How to Restore – Dropped Pluggable database (PDB) in Multitenant (Doc ID 2034953.1)” is using DUPPLICATE for that, but this requires to have all neccessary backups in one location. So I chose a different approach. My auxiliary instance will be based on the original one, so I create a Pfile from it, that I will change according to my needs:

  • choose a new db_unique_name
  • remove “control_files” parameter
  • leave create-file-destinations unchanged
  • change memory parameters to minimum values
SQL> create pfile='/tmp/initaux.ora' from spfile;

File created.
*.audit_file_dest='/u01/app/oracle/admin/orcl19/adump'
*.audit_trail='db'
*.compatible='19.0.0'
*.container_data='ALL'
*.db_block_size=8192
*.db_create_file_dest='/u01/app/oracle/oradata'
*.db_domain='support.robotron.de'
*.db_name='orcl19'
*.db_unique_name='orcl19aux'
*.db_recovery_file_dest='/u01/app/oracle/fra'
*.db_recovery_file_dest_size=10494m
*.diagnostic_dest='/u01/app/oracle'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=orcl19XDB)'
*.enable_pluggable_database=true
*.nls_language='AMERICAN'
*.nls_territory='AMERICA'
*.open_cursors=300
*.pga_aggregate_target=364m
*.processes=300
*.remote_login_passwordfile='EXCLUSIVE
*.sec_return_server_release_banner=TRUE
*.sga_target=2048m
*.undo_tablespace='UNDOTBS1'

Keeping the values for file destinations will restore the datafiles in their original paths in case of user-defined filenames. In case of OMF we will get new subdirectories according to the new db_unique_name. Either way the files will be created in the filesystem the original database is using.

With this Pfile I can now start my auxiliary instance and create a SPfile. Having a SPfile is handy because RMAN will update the “control_files” parameter accordingly when restoring the controlfiles. Of cause I have to set the ORACLE_SID to some value other than the original CDB.

[oracle@vm160 ~]$ export ORACLE_SID=aux
[oracle@vm160 ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Feb 23 14:22:46 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Connected to an idle instance.

SQL> startup nomount pfile=/tmp/initaux.ora
ORACLE instance started.

Total System Global Area 2147482320 bytes
Fixed Size                  9165520 bytes
Variable Size             486539264 bytes
Database Buffers         1644167168 bytes
Redo Buffers                7610368 bytes

SQL> create spfile from pfile='/tmp/initaux.ora';

File created.

SQL> startup nomount force
ORACLE instance started.

Total System Global Area 2147482320 bytes
Fixed Size                  9165520 bytes
Variable Size             486539264 bytes
Database Buffers         1644167168 bytes
Redo Buffers                7610368 bytes

Having that, I can now restore and recover my PDB using RMAN. Note, since we only restore the one PDB, we have to skip all the other PDBs and the PDB$SEED during recovery.

[oracle@vm160 ~]$ rman target /

Recovery Manager: Release 19.0.0.0.0 - Production on Thu Feb 23 14:27:46 2023
Version 19.18.0.0.0

Copyright (c) 1982, 2019, Oracle and/or its affiliates.  All rights reserved.

connected to target database: ORCL19 (not mounted)

RMAN> set DBID 422142402

executing command: SET DBID

RMAN> run {
1      set until time "to_date('23.02.2023 13:55:00', 'dd.mm.yyyy hh24;mi:ss')";
2      set newname for database to new;
3      restore controlfile from '/u01/app/oracle/fra/ORCL19/autobackup/2023_02_23/o1_mf_s_1129557030_kzgr8qj7_.bkp';
4      alter database mount;
5      restore database root pluggable database pdb1;
6      switch datafile all;
7      switch tempfile all;
8      recover database skip forever tablespace
9      "ORCL19PDB3":"SYSTEM","ORCL19PDB3":"SYSAUX","ORCL19PDB3":"UNDOTBS1","ORCL19PDB2":"SYSTEM","ORCL19PDB2":"SYSAUX","ORCL19PDB2":"UNDOTBS1","PDB$SEED":"SYSTEM","PDB$SEED":"SYSAUX","PDB$SEED":"UNDOTBS1";
10 }

executing command: SET until clause

executing command: SET NEWNAME

Starting restore at 2023-02-23 14:51:50
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=256 device type=DISK

[...]

Starting recover at 2023-02-23 15:08:47
using channel ORA_DISK_1

Executing: alter database datafile 5, 6, 8 offline drop
Executing: alter database datafile 12, 13, 14 offline drop
Executing: alter database datafile 15, 16, 17 offline drop
starting media recovery

archived log for thread 1 with sequence 164 is already on disk as file /u01/app/oracle/fra/ORCL19/archivelog/2023_02_23/o1_mf_1_164_kzgr5rdp_.arc
archived log for thread 1 with sequence 165 is already on disk as file /u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_3_kq6lk1ty_.log
archived log file name=/u01/app/oracle/fra/ORCL19/archivelog/2023_02_23/o1_mf_1_164_kzgr5rdp_.arc thread=1 sequence=164
archived log file name=/u01/app/oracle/fra/ORCL19/onlinelog/o1_mf_3_kq6lk1ty_.log thread=1 sequence=165
media recovery complete, elapsed time: 00:00:06
Finished recover at 2023-02-23 15:08:58

In case you have several PDBs to skip or if you do not like to type all the tablespaces by hand, you can use this handy SQL on the original database to generate the list of tablespaces to skip:

SQL> select listagg('"'||p.name||'":"'||t.tablespace_name||'"', ',')
  2  from v$pdbs p, cdb_tablespaces t
  3  where p.con_id=t.con_id
  4  and t.contents <> 'TEMPORARY'
SQL> /

LISTAGG('"'||P.NAME||'":"'||T.TABLESPACE_NAME||'"',',')
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
"ORCL19PDB3":"SYSTEM","ORCL19PDB3":"SYSAUX","ORCL19PDB3":"UNDOTBS1","ORCL19PDB2":"SYSTEM","ORCL19PDB2":"SYSAUX","ORCL19PDB2":"UNDOTBS1","PDB$SEED":"SYSTEM","PDB$SEED":"SYSAUX","PDB$SEED":"UNDOTBS1"

At this point we could open the auxiliary instance using the “open resetlogs” clause. But wait, we restored the controlfile from the original database so we have the same filenames for the redo members. And we are on the same host. Clearing those redo files might not be the best option. It would simply overwrite the redo files of our original database and we would end up doing another incomplete recovery… My solution is to create a new controlfile with modified redo members. As a basis I backup the controlfile to trace, change the db_name right away and restart my auxiliary instance to nomount.

SQL> alter database backup controlfile to trace as '/tmp/create_ctrl_aux.sql' resetlogs;

Database altered.

SQL> alter system set db_name="AUX" scope=spfile;

System altered.

SQL> startup nomount force
ORACLE instance started.

Total System Global Area 2147482320 bytes
Fixed Size                  9165520 bytes
Variable Size             486539264 bytes
Database Buffers         1644167168 bytes
Redo Buffers                7610368 bytes

Then I modified the “create controlfile” statement

  • set the new database name
  • remove all the files, that I didn’t restore
  • remove redo file names (OMF) – or – change redo file names

The final statement is this:

CREATE CONTROLFILE REUSE set DATABASE "AUX" RESETLOGS  ARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 1024
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
  GROUP 1 SIZE 200M BLOCKSIZE 512,
  GROUP 2  SIZE 200M BLOCKSIZE 512,
  GROUP 3 SIZE 200M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
  '/u01/app/oracle/oradata/ORCL19AUX/datafile/o1_mf_system_kzgvw29p_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/datafile/o1_mf_sysaux_kzgvw291_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/datafile/o1_mf_undotbs1_kzgvw2b9_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/datafile/o1_mf_users_kzgvw2f8_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_system_kzgw69jf_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kzgw69jz_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kzgw69kb_.dbf',
  '/u01/app/oracle/oradata/ORCL19AUX/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_kzgw69h9_.dbf'
CHARACTER SET AL32UTF8
;

Finally, I create a new controlfile, open the auxiliary database and unplug the PDB keeping their datafiles.

SQL> CREATE CONTROLFILE REUSE set DATABASE "AUX" RESETLOGS  ARCHIVELOG
[...]

Control file created.

SQL> alter database open resetlogs;

Database altered.

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       MOUNTED
         3 PDB1                           MOUNTED
         4 ORCL19PDB3                     MOUNTED
         5 ORCL19PDB2                     MOUNTED
SQL> alter pluggable database PDB1 unplug into '/tmp/pdb1.xml';

Pluggable database altered.

SQL> drop pluggable database PDB1 keep datafiles;

Pluggable database dropped.

At this point we can transfer the restored PDB back to its original container. You can decide, if you want to use the restored datafiles right away or if you create another copy of them. Either way it is a simple one-liner.

SQL> create pluggable database PDB1 using '/tmp/pdb1.xml' copy;

Pluggable database created.

Or

SQL> create pluggable database PDB1 using '/tmp/pdb1.xml' nocopy tempfile reuse;

Pluggable database created.

Last step is to open the PDB again and check it’s contents.

SQL> alter  pluggable database PDB1 open;

Pluggable database altered.

SQL> alter  pluggable database PDB1 save state;

Pluggable database altered.

SQL> alter session set container=pdb1;

Session altered.

SQL> select count(*) from hr.employees;

  COUNT(*)
----------
       108

Et voila, missing accomplished. The very last step now is removing the auxiliary instance.

SQL> startup mount restrict exclusive force

SQL> drop database;

Whoever said, Multitenant will make DBA’s life easier did not do such a restore 🙂 … But to be fair, there are lot of scenarios where Multitenant is very handy, I must admit.

What data can a common user see in Oracle Multitenant

With the introduction of the Oracle Multitenant architecture, some new dictionary views were introduced to reflect the new layer. Beside the well-known views prefixed by USER_, ALL_ and DBA_ there’s now a fourth set of views prefixed by CDB_. The structure of the views is very similar, but the contents depend on the users privileges.

  • USER_: Everything the logged on user owns
  • ALL_ : Everything the logged on user has privileges on
  • DBA_: Everything belonging to the current (pluggable) database
  • CDB_: Everything belonging to the CDB when queried from CDB$ROOT, similar to DBA_ views when queried from within a pluggable database

Of cause there are V$-views containing container data too, that makes up a large set of containerized views.

SQL> SELECT count(*) FROM cdb_views WHERE container_data = 'Y';

  COUNT(*)
----------
      3118

Now let’s verify, what data can be seen in these views at the different levels, CDB$ROOT or PDB. I created a common user with the appropriate privileges to check that.

SQL> create user c##marco identified by "R0b0tr0N##";

User created.

SQL> grant select any dictionary, create session, set container to c##marco container=all;

Grant succeeded.

Having that, it is time to check what the newly created user can read from the dictionary views at the CDB$ROOT and the PDB level.

SQL> conn c##marco/********
Connected.
SQL> show con_name

CON_NAME
------------------------------
CDB$ROOT
SQL> select con_id, name from v$datafile
  2  order by 1;

    CON_ID NAME
---------- --------------------------------------------------------------------------------------------------------------
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6ld1qb_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6lbyd0_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6l8vf4_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_users_kq6ld4xm_.dbf

4 rows selected.

SQL> alter session set container=pdb1;

Session altered.

SQL> select con_id, name from v$datafile;

    CON_ID NAME
---------- ------------------------------------------------------------------------------------------------------------------------
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_system_kq6nz8kh_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kq6nz8kt_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kq6nz8kv_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_ksfwjspy_.dbf

Obviously the user can see only data belonging to the current container. From within the CDB$ROOT only content from the CDB$ROOT is visible, nothing from any PDB. At the PDB level the user can only see the data for that specific PDB. The later makes totally sense, the first too, at a second glance. The PDBs might be sensitive somehow, their contents should not be exposed to everyone by default. Of cause this can be changed. As mentioned, only data for the current container can be seen by default. The CONTAINER_DATA setting for an user is used to control that.

SQL> conn / as sysdba
Connected.
SQL> alter user c##marco set container_data=all container=current;

User altered.

SQL> SELECT username,
  2         owner,
  3         object_name,
  4         all_containers,
  5         container_name
  6  FROM   cdb_container_data
  7  WHERE  username = 'C##MARCO'
  8  ORDER BY 1,2,3;

USERNAME             OWNER                OBJECT_NAME          A CONTAINER_NAME
-------------------- -------------------- -------------------- - --------------------
C##MARCO                                                       Y

Now the common user C##MARCO is able to see the CDB$ROOT data as well as the data from all the PDBs. With”CONTAINER_DATA=ALL” the setting applies to all existing PDBs and will apply for all future PDBs.

SQL> conn c##marco/********
Connected.
SQL> show con_name

CON_NAME
------------------------------
CDB$ROOT
SQL> select con_id, name from v$datafile
  2 order by 1;

    CON_ID NAME
---------- --------------------------------------------------------------------------------------------------------------
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6ld1qb_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6lbyd0_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6l8vf4_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_users_kq6ld4xm_.dbf
         2 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6m8z4c_.dbf
         2 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6m8z7g_.dbf
         2 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6m8zgv_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kq6nz8kv_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_ksfwjspy_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_system_kq6nz8kh_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kq6nz8kt_.dbf
         4 /u01/app/oracle/oradata/ORCL19/EF6055BDD31101A7E053A024100A1966/datafile/o1_mf_sysaux_ks5op8o9_.dbf
         4 /u01/app/oracle/oradata/ORCL19/EF6055BDD31101A7E053A024100A1966/datafile/o1_mf_undotbs1_ks5op8o9_.dbf
         4 /u01/app/oracle/oradata/ORCL19/EF6055BDD31101A7E053A024100A1966/datafile/o1_mf_system_ks5op8n8_.dbf
         5 /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_system_ks5pktn8_.dbf
         5 /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_sysaux_ks5pktn9_.dbf
         5 /u01/app/oracle/oradata/ORCL19/EF608A6519330511E053A024100A91E2/datafile/o1_mf_undotbs1_ks5pktnb_.dbf

Since this setting is somewhat global, there’s a method to restrict the visibility at PDB level. To outline that, I first remove the setting and replace it with a more granular one.

SQL> alter user c##marco set container_data=default container=current;

User altered.

SQL> SELECT username,
  2         owner,
  3         object_name,
  4         all_containers,
  5         container_name
  6  FROM   cdb_container_data
  7  WHERE  username = 'C##MARCO'
  8  ORDER BY 1,2,3;

no rows selected

SQL> alter user c##marco set container_data=(CDB$ROOT,PDB1) container=current;

User altered.

SQL> SELECT username,
  2         owner,
  3         object_name,
  4         all_containers,
  5         container_name
  6  FROM   cdb_container_data
  7  WHERE  username = 'C##MARCO'
  8  ORDER BY 1,2,3;

USERNAME             OWNER                OBJECT_NAME          A CONTAINER_NAME
-------------------- -------------------- -------------------- - --------------------
C##MARCO                                                       N PDB1
C##MARCO                                                       N CDB$ROOT

The value for the “ALL_CONTAINERS” is now ‘N’ and every container, that the user has access to, is listed as a separate record. Only data from CDB$ROOT and PDB1 is now being returned by the test query. Data from other PDBs is excluded. Also data from newly created PDBs will not be visible.

SQL> conn c##marco/********
Connected.
SQL> select con_id, name from v$datafile;

    CON_ID NAME
---------- --------------------------------------------------------------------------------------------------------------
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_system_kq6l8vf4_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_sysaux_kq6lbyd0_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_undotbs1_kq6ld1qb_.dbf
         1 /u01/app/oracle/oradata/ORCL19/datafile/o1_mf_users_kq6ld4xm_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_system_kq6nz8kh_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_sysaux_kq6nz8kt_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_undotbs1_kq6nz8kv_.dbf
         3 /u01/app/oracle/oradata/ORCL19/ED7F830814134AADE053A024100A5D08/datafile/o1_mf_users_ksfwjspy_.dbf

So this is way to restrict the visibility of data of different containers for common users. This might be handy for monitoring users and sensitive databases.

On top of that, the 19.10 Release Update introduced a parameter CONTAINER_DATA. The docs state, that for extended data-linked Oracle-supplied data dictionary objects data from CDB$ROOT as well as PDBs is returned with the default setting of ‘ALL’. Changing the value to ‘CURRENT’ or ‘CURRENT_DICTIONARY’ restrict the data returned by queries to the current PDB. I tried to verify that with the above example, but did not see any difference.

SQL> conn c##marco/********
Connected.
SQL> show parameter container_data

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
container_data                       string      ALL

SQL> select distinct con_id from cdb_objects;

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

SQL> alter session set container_data=current;

Session altered.

SQL> select distinct con_id from cdb_objects;

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

SQL> alter session set container=pdb1;

Session altered.

SQL> show parameter container_data

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
container_data                       string      CURRENT

SQL> select distinct con_id from cdb_objects;

    CON_ID
----------
         3

SQL> alter session set container_data=all;

Session altered.

SQL> select distinct con_id from cdb_objects;

    CON_ID
----------
         3

I have no clue, what that parameter is really meant for. There is a MOS note “Query to ALL_SYNONYMS takes very long time after 19c upgrade (Doc ID 2915211.1)“, that mentions some performance issues that can be worked around by setting this parameter. Maybe that is the main use case. If anybody knows, how that parameter should be used and when, I am happy to learn about that.