Showing posts with label Recovery. Show all posts
Showing posts with label Recovery. Show all posts

Monday, July 25, 2022

Recover a TRUNCATE TABLE Statement Using FLASHBACK DATABASE Feature

Scenario Summary:

This was a real production scenario I've gone through to restore big number of tables been truncated mistakenly by someone who ran a script that suppose to run on test environment. The DB version is 11.2.0.4 and the Flashback Database feature was turned ON.

Scenario Details:

- An Architect mistakenly ran a script to truncate multiple tables on production DB instead of running it on Test DB --Was opening multiple windows at the same time, one of them was Prod.
- The issue been identified few hours later.
- Because of the late identification of the issue, other applications which were not impacted by that truncation has loaded data on other tables sharing the same tablespace of the truncated tables.
- Due to the storage free space constraint on the same server and because of other infrastructure limitations, it was very difficult to restore the impacted tablespace to a point in time in the same server or even in a different server.
- I decided to use the Flashback Database approach to restore those tables, as I managed to get a downtime for 1 hour on the impacted DB.


Facts: 

- If your DB version is 12c and above I recommend you to use "RECOVER TABLE" RMAN command to restore the truncated tables instead of using Flashback method explained in this article; unfortunately, because my impacted DB version was 11.2.0.4, "Recover Table" feature is not available.

- When it comes to recover from a TRUNCATE statement in 11g, you will end up having few recoverability options to select from. Up to Oracle 19c, still "FLASHBACK TABLE" feature cannot recover a TRUNCATE operation, because this feature is dependent on UNDO data which get invalidated if a DDL get executed against the table which change its architecture, TRUNCATE is doing the same by moving the table's high watermark.
Available options to recover a TRUNCATE statement can be using "Flashback Data Archive" feature, if it's enabled before the truncation, otherwise the best option you will have in your arsenal is to flash back the whole DB, then export the impacted tables, then flash forward/recover the DB to return it back to its latest status, then import the impacted tables. This solution involves a downtime on the whole DB. Another option you can use; is to restore the whole tablespace --where the truncated tables reside to a point in time to a different/new DB and export the truncated tables from there and import them back to the impacted DB.

- To Flash back a database using Flashback Database feature, the following requirements should be met:

    1- Flashback Database feature should be turned on before the truncation.
    2- All the Flashback Logs generated up to the time before the truncation should be available on the server (FRA).
    3- All the Archive Logs generated up to before the truncation time should be available on the server.(either as files or backup pieces).

V.Imp Note:
Once you come to know about the truncation incident, DO NOT change the DB structure by CREATE OR DELETE any TABLESPACE or DATAFILE, or SHRINK a DATAFILE or RECREATE the CONTROLFILE as this may invalidate the recovery when you try to recover the DB to flash it forward to the original status!


Technical Steps:

Check the prerequsites for the Flashback to succeed:

- Check how far the DB can be flashed back:

SQL> col OLDEST_FLASHBACK_SCN for 999999999999999999999
           select * from V$FLASHBACK_DATABASE_LOG;

- Increase the flashback retention to avoid the auto-deletion of the Flashback archive files: [e.g. 4 days]

SQL> alter system set db_flashback_retention_target=6000;

- Stop/Disable any cron or backup jobs that can delete any RMAN backups or archivelogs: [Until the truncated tables get restored]

Get the SCN just before the truncation time: [Just before the errorenous truncate]

My Truncation time happened at 11/07/2022 18:00:12

-- Convert Timestamp to SCN

SQL> col scn for 999999999999999999999999
           select timestamp_to_scn(to_timestamp('11/07/2022 18:00:00','DD/MM/YYYY HH24:MI:SS')) as scn from dual;





- Check the availability of the archive logs required to cover the flashback period: [Requirement for Flashback to succeed]

SQL> select SEQUENCE#,FIRST_TIME from v$archived_log where FIRST_TIME>sysdate-1;

For my bad luck only the last 6 hours archive logs are available on the server, which means I need to restore the older archives to cover the last 24 hours needed for the flashback to work:

- Checking the needed Archive logs that are not available and restore them:

SQL> select SEQUENCE#,FIRST_TIME from v$archived_log where FIRST_TIME>sysdate-1 and name is  null;


 

- Restore the needed Archive logs from TAPE to the server: [Below tape parameters are specific to my environment]

RMAN> run
{
allocate channel 'dev_1' type 'sbt_tape' parms 'SBT_LIBRARY=/opt/omni/lib/libob2oracle8_64bit.so,ENV=(OB2BARTYPE=Oracle8,OB2APPNAME=DB01,OB2BARLIST=RDB01_ORCL_Arch _Log_SO)';
#send device type 'sbt_tape' 'OB2BARHOSTNAME=rdb01';
set archivelog destination to '/backup/arch/';
restore archivelog from sequence 470 until sequence 485;
}



-- Catalog the restored archivelogs:

RMAN> catalog start with '/backup/arch';


It's always wise to take a full DB backup before starting the flashback task, just in case the DB have a problem with flashing it forward.



Flashback the DB to before the tables truncation time:

-- Note down the current SCN:
SQL> col current_scn for 999999999999999999999999
           select current_scn from v$database;


6605314662302

-- Flashback the DB to before the Truncation:

SQL> alter system checkpoint;
SQL> shu immediate
           startup mount

>>From rman:
# rman target /
RMAN> FLASHBACK DATABASE TO SCN 6607836579369;

-- Open the DB in Read Only mode:
RMAN> alter database open read only;


Export the truncated tables: [Using legacy exp as expdp will not work in Read Only mode]

# exp  \'/ as sysdba\' TABLES=PL.F_PRICE,PL.E_CODES,PL.ET_STATUS,PL.BATCH_SO,PL.FL_INFO DIRECT=y CONSISTENT=y STATISTICS=NONE FEEDBACK=100000 RESUMABLE=y RESUMABLE_NAME=DBA_BUNDLE_EXPORT RESUMABLE_TIMEOUT=86400 FILE=/backup/EXPORT_TABLES.dmp log=/backup/EXPORT_TABLES.log

Note: Because legacy exp tool doesn't provide parallelism option, you can make your own parallelism by running the same exp command from different sessiosns providing different set of tables in each session and run them at the same time.

Once the export is done, Return the DB back to its original state before the flashback:

-- First use recover technique to recover the DB until the latest REDOLOG:
   SQL> SHU IMMEDIATE
   SQL> STARTUP MOUNT

  # rman target /
   RMAN> recover database;
   RMAN> alter database open;

Unfortunately, in my case a new tablespace was created after the truncation of tables which made the above command to fail, so instead of using "recover database" command I had to use the FLASHBACK command using the SCN I noted down before the first shutdown of the DB.

If recover technique didn't work use Flashback:
-- From mount mode:
RMAN> FLASHBACK DATABASE TO SCN 6605314662302;
RMAN> alter database open resetlogs;

Once the DB is open in RW mode,

Import the dump file to recover the truncated tables:


-- Disable the triggers on the truncated tables first: [If found]
SQL> select 'alter trigger '||owner||'.'||trigger_name||' disable;' from dba_triggers where owner='PL' and table_name in ('F_PRICE','E_CODES','ET_STATUS','BATCH_SO','FL_INFO');

-- You can also set the tables into NOLOGGING mode (if you have no data guard setup)

-- Execute the import commands:
I distributed the tuncated tables on three sets based on their size, then execute each set in the background to have the tables get imported in parallel:

Note: In my real case I used INDEXES=N parameter during the import as I've collected the DDL for all indexes on the impacted tables before the import, then dropped them, and lastly created them back after the import with high PARALLEL degree and NOLOGGING option to save the time.

# Set1:
nohup imp  \'/ as sysdba\' fromuser=PL touser=PL TABLES=F_PRICE, E_CODES  file=/backup/EXPORT_TABLES.dmp log=/backup/imp_PL_truncated_tables_set1.log ignore=y  RESUMABLE=Y RESUMABLE_NAME=SET1_SUSPENDED resumable_timeout=3600 commit=y buffer=10240 feedback=10000  > nohup1.log 2>&1 &

 

# Set2:
nohup imp  \'/ as sysdba\' fromuser=PL touser=PL TABLES=BATCH_SO  file=/backup/EXPORT_TABLES.dmp log=/backup/imp_PL_truncated_tables_set2.log ignore=y  RESUMABLE=Y RESUMABLE_NAME=SET2_SUSPEND  resumable_timeout=3600 commit=y buffer=10240 feedback=10000  > nohup1.log 2>&1 &

 

# Set3:
nohup imp  \'/ as sysdba\' fromuser=PL touser=PL TABLES=FL_INFO  file=/backup/EXPORT_TABLES.dmp log=/backup/imp_PL_truncated_tables_set3.log ignore=y  RESUMABLE=Y RESUMABLE_NAME=SET3_SUSPEND  resumable_timeout=3600 commit=y buffer=10240 feedback=10000  > nohup1.log 2>&1 &


After the completion of the import:

-- Enable the constraints: [In case the import fails to enable them]
SQL> select 'alter table pnl_owner.'||table_name||' ENABLE CONSTRAINT '|| constraint_name||' ;' from dba_constraints where owner='PNL_OWNER' and table_name in ('ADV_STG_FUEL_INFO','DIM_FUEL_PRICE','ERROR_CODES','PNL_ETL_JOB_STATUS','DIM_INFLIGHT','SYS_C0029531 DIM_STANDARD_COSTS','PROCESS_EXECUTION_STATISTICS','STG_CARGO','STG_FUEL_INFO','STG_RCT_INFO','WEEKLY_CARGO','DIM_CURRENCY','DIM_GLOBAL_VAL','DIM_OTHER_INC','DIM_SERVICE_TYPE_AIMS','PROCESS_CHECKLIST','SNAPSHOT_WORKING','STG_BSP_REVENUE');

-- Enable back the triggers: [In case you disabled them before the import]
SQL> select 'alter trigger '||owner||'.'||trigger_name||' enable;' from dba_triggers where owner='PNL_OWNER' and STATUS='DISABLED' and table_name in ('ADV_STG_FUEL_INFO','DIM_FUEL_PRICE','ERROR_CODES','PNL_ETL_JOB_STATUS','BATCH_STATUS','DIM_INFLIGHT','SYS_C0029531 DIM_STANDARD_COSTS','PROCESS_EXECUTION_STATISTICS','STG_CARGO','STG_FUEL_INFO','STG_RCT_INFO','WEEKLY_CARGO','DIM_CURRENCY','DIM_GLOBAL_VAL','DIM_OTHER_INC','DIM_SERVICE_TYPE_AIMS','PROCESS_CHECKLIST','SNAPSHOT_WORKING','STG_BSP_REVENUE','WEEKLY_UNUTILIZED_BLOCK','STANDARD_BASE_AUDIT','DIM_PROV_NON_REPATRIABLE_FUNDS','STG_PAX_INFO','DIM_DOC_RATES','APEX_UPLOAD_STATUS','DAILY_UNUTLZD_BLOCK','DIM_ROUTES','WEEKLY_REVENUE','DATA_LOADING_INFO','DIM_CREW_LO','DIM_DATE','DIM_FLT_SCHEDULE','DIM_REV_CODES','EMAIL_LOG','FINAL_STANDARDS','STANDARD_BASE','DIM_HEDGED_QTY','FLT_SECTOR_PAIR','DIM_AIRPORTS','THRESHOLD_SETTINGS','AUDIT_BATCH_STATUS','PNL_ETL_EMAIL_CONFIG','MONTHLY_ACTUALS_FEEDER','STG__OAL_NO_SHOW_PAX','STG_MEAL_REV_CNT','STG_OAL_NO_SHOW_PAX','STG_RCT_DETAILS','PARAMRTAG','DIM_INFLIGHT_PARAM','STG_UNUTLZD_BLOCK');

Reset back the Flashback retention to its original setting:
SQL> alter system set db_flashback_retention_target=3000;

Enable back the disabled cron and backup jobs which you may disabled before the flashback:

Gather statistics on the imported tables:
EXEC DBMS_STATS.GATHER_TABLE_STATS('PL', 'F_PRICE');
EXEC DBMS_STATS.GATHER_TABLE_STATS('PL', 'E_CODES');
EXEC DBMS_STATS.GATHER_TABLE_STATS('PL', 'ET_STATUS');
EXEC DBMS_STATS.GATHER_TABLE_STATS('PL', 'BATCH_SO');
EXEC DBMS_STATS.GATHER_TABLE_STATS('PL', 'FL_INFO');

It's always wise to take a Full DB Backup after such activity 😀



 

Wednesday, January 19, 2022

Automatic Block Corruption Recovery From the Standby DB | How to Make Sure it's Enabled

 Since Oracle 11g a new feature was introduced to automatically recover the physical corrupted blocks online from the Standby DB immediately once the physical corruption occurs on the Primary without any intervention from the DBA. But in order for this feature to work, The DBA has to make sure that the following two prerequisites are fulfilled in the environment:

1- Active Data Guard should be enabled, where the standby will be in Open mode while the media recovery of redo logs is running. [This requires ADG license].

On the Standby:

SQL> ALTER DATABASE OPEN;
SQL> RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE NODELAY DISCONNECT;

2- db_block_checksum initialization parameter should be set to at least TYPICAL in both the Primary and the Standby (which is the default value).

But what about db_block_checking parameter? Should it be set as well?

Answering this question took me hours of searching, Although Oracle Maximum Availability Architecture recommends setting db_block_checking parameter to MEDIUM or FULL; it has nothing to do with the activation of Automatic Block Corruption which only involved in recovering PHYSICAL corrupted blocks not the Logical ones, db_block_checking is for enabling the checking of LOGICAL corruption.

Please note that the default value for db_block_checking is FALSE, and this is because when it get set to MEDIUM or FULL it can cause a performance impact from 5% to 20% based on the nature of the workload on the DB.
Getting to know about Logical Corruption immediately is a nice thing, but it will come at the expense of the performance!

Another question, why Oracle enables the check for physical block corruption by default but not the Logical corruption one?

This because Physical corruption are most common to happen than the Logical ones, also as mentioned above checking logical corruption will consume significant resources as Oracle will need to dig deeper inside each block to check the data consistency.

References:

Best Practices for Corruption Detection, Prevention, and Automatic Repair - in a Data Guard Configuration (Doc ID 1302539.1)

Oracle Maximum Availability Architecture White Paper

Thursday, June 11, 2020

Re-execution of Duplicate command on 19c DB fails with RMAN-06054: media recovery requesting unknown archived log

Problem:

On a 19.5 database, I was trying to duplicate a database from an RMAN backup, first time the duplicate failed after restoring 90% of the datafiles due to space limitation on the underlying datafiles location, I re-ran the same duplicate command for the second time without deleting the already restored datafiles to see if the 19c RMAN will be able to continue the previous duplicate without problems using this command:

I've restarted the auxiliary DB again in mount mode and re-ran the duplicate command:
run {
ALLOCATE AUXILIARY CHANNEL ch1 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch2 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch3 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch4 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch5 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch6 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch7 DEVICE TYPE DISK;
ALLOCATE AUXILIARY CHANNEL ch8 DEVICE TYPE DISK;
duplicate database to "AWSNFR"  backup location '/bkp' nofilenamecheck
UNTIL TIME "TO_DATE('31/05/2020 15:20:52', 'DD/MM/YYYY HH24:MI:SS')";
}


But the second run of the duplicate command failed with the following error after restoring the rest of datafiles, requesting a very old archivelog for recovering the database:

.....
Starting restore at 07-Jun-2020 11:04:26
creating datafile file number=19 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.283.1039812437
creating datafile file number=20 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.284.1039812457
creating datafile file number=21 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.285.1039812475
creating datafile file number=24 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.288.1039812529
creating datafile file number=25 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.289.1039812549
creating datafile file number=27 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.291.1039812587
creating datafile file number=28 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.292.1039812605
creating datafile file number=42 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.306.1039812873
creating datafile file number=43 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.307.1039812891
creating datafile file number=57 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.321.1039813155
creating datafile file number=58 name=/awsnfr/AWSNFRDB1/datafiles/p_fz_data.322.1039813173

......

Finished restore at 07-Jun-2020 12:05:28
.....
starting media recovery

unable to find archived log
archived log thread=1 sequence=7


RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of Duplicate Db command at 06/07/2020 12:05:31
RMAN-05501: aborting duplication of target database
RMAN-03015: error occurred in stored script Memory Script
RMAN-06054: media recovery requesting unknown archived log for thread 1 with sequence 7 and starting SCN of 2470350


Analysis:

You can notice from the above log, when duplicate command ran again, it started with re-creating (not restoring) some blank datafiles (in green color).

After the failure of the duplicate command, I ran the following command and figured out that those datafiles (in green color) are the only ones that having very low checkpoint_time and checkpoint_change# than the other datafiles:

col name for a60
col CREATION_TIME for a20
col checkpoint_time for a20
select FILE#||'|'||name name,fuzzy, creation_change#,
to_char(creation_time, 'DD-MON-YYYY HH24:MI:SS') as creation_time,
to_char(checkpoint_time, 'DD-MON-YYYY HH24:MI:SS') as checkpoint_time,
to_char(checkpoint_change#, '999999999999999999999') as checkpoint_change#
from v$datafile_header;


Solution:

I decided to restore those datafiles manually from RMAN:

RMAN> restore datafile 19,20,21,24,25,27,28,42,43,57,58;

RMAN> recover database;
This will recover the database starting from the right sequence till it fail when no more archivelogs are available inside the backup.

Then, because I was running an RMAN duplicate on a different server, I need to complete the rest of in-complete duplicate steps manually:

1- Generate a create controlfile script:
RMAN> alter database backup controlfile to trace as '/home/oracle/control.trc';
 

2- Edit the controlfile trace by replacing the NORESETLOGS keyword with RESETLOGS then delete the rest of script contents :

# vi /home/oracle/control.trc

STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "
AWSNFR" RESETLOGS 
....
CHARACTER SET AL32UTF8
;


Then remove all the lines below CHARACTER SET AL32UTF8 ; lines:

Make sure that all datafiles, redo logs are pointing to the right exist paths on the target server.

3- Shutdown the database and execute the controlfile creation script "which we modified above" to overwrite the current clone controlfile:
RMAN> SHUTDOWN IMMEDIATE
RMAN> @/home/oracle/control.trc


4- Open the database with resetlogs option:  
RMAN> alter database open resetlogs;

Voilà! Database opened.


Add a tempfile to the Temporary tablespace:

RMAN> alter tablespace temp add tempfile;


Conclusion:
In Oracle 19c, RMAN can continue the duplicate from where it stopped, it will catch from where it stopped in the previous execution, but the catch here, it will not be able to re-restore the datafiles that were in the middle restoration during the last duplicate execution, so it will try to create them as blank datafiles, during the recovery it will try to recover these datafiles from the time they were created on the source DB! Of course most probably those archivelogs are since the creation of the source database itself, and they are not available anymore. The DBA has to correct this behavior by restore those datafiles manually from the same RMAN backup the duplicate was using. Still, the re-execution of the duplicate operation on 19c is not a fail proof!

Wednesday, May 20, 2020

Duplicate of a Standby DB fails with ORA-19845

While creating a Standby database on a RAC environment from another RAC database using duplicate method I was getting this weird error:

RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of Duplicate Db command at 05/20/2020 14:20:14
RMAN-05501: aborting duplication of target database
RMAN-03015: error occurred in stored script Memory Script
ORA-19845: error in backupControlFile while communicating with remote database server
ORA-17628: Oracle error 1580 returned by remote Oracle server
ORA-01580: error creating control backup file
ORA-19660: some files in the backup set could not be verified
ORA-19661: datafile 0 could not be verified
ORA-19845: error in backupControlFile while communicating with remote database server
ORA-17628: Oracle error 1580 returned by remote Oracle server
ORA-01580: error creating control backup file


I was running the duplicate command from the primary DB (Node1)

After long investigation I figured out that the Snapshot controlfile location is not shared between both RAC instances (on the primary side):

RMAN> show SNAPSHOT CONTROLFILE NAME;

RMAN configuration parameters for database with db_unique_name SPRINTS are:
CONFIGURE SNAPSHOT CONTROLFILE NAME TO '/cloudfs/backup/sprint/snapcf_sprint.f';


That location "/cloudfs/backup/sprint" was only available on Node1 and not available on Node2

I've simply gone to Node2 and created the full location path and the duplicate succeeded!
On Node2:

# mkdir -p /cloudfs/backup/sprint
# chown oracle /cloudfs/ -R


Conclusion:
As a rule of thumb, the SNAPSHOT CONTROLFILE location should be available on all RAC nodes. Even if it's not shared between all nodes, it just needs to be exist!


Saturday, July 7, 2018

Auditing Using LogMiner

Scenario: Let's suppose you want to investigate a recent case on a database whereas auditing is NOT enabled, or it's enabled but it didn't cover the activities you are looking for, in this case, you still have the ability to dig and find those activities using LogMiner feature.

LogMiner is a free feature (No license required) available in both Standard and Enterprise Editions, that can analyze the contents of archive log files and display (SYS, non-SYS, COMMITTED, NON-COMMITTED) transactions that happened during a specific period of time. It can also be used to recover a logical corruption of one or multiple transactions as it also contains the reverse (UNDO) statement of all transactions (similar to flashback transaction query).

I'll start with building the test scenario, a new user will be created and by that user, I'll execute some DDLs and DMLs along with wrong statements to figure out what are the statements that LogMiner can fetch for us.

-- Login as SYSDBA and Create a new user "AA" for testing: <let's see if this CREATE USER statement by SYSDBA will get captured>
06-Jul-2018 15:16:15 SYS@orcl> create user AA identified by "aa#12345";
06-Jul-2018 15:16:30 SYS@orcl> grant connect,resource to aa;

-- Connect to the new user:
06-Jul-2018 15:16:37 SYS@orcl> conn aa/aa#12345
Connected.

06-JUL-18 AA@orcl> create table a (a number);
Table created.

-- execute a wrong insert statement: <let's see if wrong statements are getting captured>
06-JUL-18 AA@orcl> insert into a('1');
insert into a('1')
              *
ERROR at line 1:
ORA-00928: missing SELECT keyword

-- execute a committed insert transaction:
06-JUL-18 AA@orcl> insert into a values (2);
1 row created.

06-JUL-18 AA@orcl> commit;
Commit complete.

-- execute a commited delete transaction:
06-JUL-18 AA@orcl> delete from a;
1 row deleted.

06-JUL-18 AA@orcl> commit;
Commit complete.

-- Execute a commited update tansaction that update nothing: 
06-JUL-18 AA@orcl> update a set a=5 where a=1;
0 rows updated.

-- Execute a truncate table statement:
06-JUL-18 AA@orcl> truncate table a;
Table truncated.

-- Create a procedure with compilation errors:
06-JUL-18 AA@orcl> create or replace procedure p as select * from a;
  2  /
Warning: Procedure created with compilation errors.

-- Execute a non-committed insert statement:
06-JUL-18 AA@orcl> insert into a values (3);
1 row created.

06-JUL-18 AA@orcl> rollback;
Rollback complete.

-- Now I'm done let's check the time at the end of this demo:
06-JUL-18 AA@orcl> !date
Fri Jul  6 15:34:51 GST 2018

And now let's use the LogMiner to reveal the transaction details that happened during that demo by user AA.

Step 1: Turn on the database level Supplemental Logging:

-- First check if the Supplemental Logging is already enabled, if supplemental_log_data_min shows "YES" this means it's ENABLED, then better do NOT run the last step of deactivating it:

SQL> SELECT supplemental_log_data_min FROM v$database;
SUPPLEME
--------
NO

-- supplemental_log_data_min shows "NO", so we have to enable the database level Supplemental Logging:
SQL> ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;

Step 2: Provide the date & time range of the transactions you want to view, try to make the range as short as possible:

SQL> begin
dbms_logmnr.start_logmnr(
starttime => '06-Jul-2018 15:16:00',
endtime => '06-Jul-2018 15:36:00',
options => dbms_logmnr.dict_from_online_catalog + dbms_logmnr.continuous_mine + dbms_logmnr.print_pretty_sql + dbms_logmnr.committed_data_only
);
end;
/

Note: Specifying "dbms_logmnr.committed_data_only" option means to look for committed transactions only, if you really interested in fetching rolled back transaction, just remove "dbms_logmnr.committed_data_only" from the list of options.


Step 3: Query V$LOGMNR_CONTENTS to get the information you need:

SQL>    set linesize 170 pages 100
col TIMESTAMP for a20
col sql_redo for a40
col sql_undo for a40
col username for a20
col os_username for a20
col MACHINE_NAME for a20
select xid,to_char(timestamp,'DD-MON-YY HH24:MI:SS') TIMESTAMP,
username,OS_USERNAME,MACHINE_NAME,sql_redo,sql_undo,ROLLBACK from v$logmnr_contents
where SEG_OWNER='AA';

=> sql_redo: the statement it self.
=> sql_undo: the reverse statement that can be used to rollback the original statement.
-- You can also search by Segment Name "SEG_NAME='A'" or by operation "operation in ('INSERT','UPDATE','DELETE')"

XID TIMESTAMP       USERNAME  OS_USERNAME  MACHINE_NAME SQL_REDO
---------------- -------------------- --------- ------------ ------------ ----------------------------------------
SQL_UNDO
----------------------------------------
09001900E39D0000 06-JUL-18 15:16:30   UNKNOWN  UNKNOWN UNKNOWN create user aa identified by  VALUES 'S:
D4D17D1076EC7C6F5B1D68FBCB244E8789691A14
6F5209DF2A51AC579007;E3EE1448DE26764F' ;


08001E002E830000 06-JUL-18 15:16:57   UNKNOWN  UNKNOWN UNKNOWN create table a (a number);


0A002100A8510600 06-JUL-18 15:17:34   UNKNOWN  UNKNOWN UNKNOWN insert into "AA"."A"
  values
"A" = 2;
delete from "AA"."A"
 where
    "A" = 2 and
    ROWID = 'AAAjrvAAEAAAACvAAA';

09000600E09D0000 06-JUL-18 15:17:43   UNKNOWN  UNKNOWN UNKNOWN delete from "AA"."A"
  where
"A" = 2 and
ROWID = 'AAAjrvAAEAAAACvAAA';
insert into "AA"."A"
 values
    "A" = 2;

05000D00AC800000 06-JUL-18 15:18:21   UNKNOWN  UNKNOWN UNKNOWN truncate table a;


0A0007004D510600 06-JUL-18 15:18:45   UNKNOWN  UNKNOWN UNKNOWN create or replace procedure p as select
* from a;;
6 rows selected.

As you can see, LogMiner fetches all kind of statements that made changes to the database including DML & DDLs along with the statements executed with SYSDBA privilege.
Any other statements that don't make any change to the database like SELECT and failed SQL statements along with the DML statements that didn't make any actual change to the data will not be shown by the LogMiner.

Once you are done:

- End the Log Miner Session and flush the contents of v$logmnr_contents:
SQL> execute dbms_logmnr.end_logmnr();
SQL> select * from v$logmnr_contents;
No Rows should be returned.

-Deactivate the database level Supplemental Logging:
In case the SUPPLEMENTAL LOG DATA was already enabled in step 1, then don't execute this step, replication software like Oracle Streams & golden gate depends on the SUPPLEMENTAL LOG DATA in order to perform the replication.

SQL> ALTER DATABASE drop SUPPLEMENTAL LOG DATA;

SQL> SELECT supplemental_log_data_min,supplemental_log_data_pk,supplemental_log_data_ui,supplemental_log_data_fk,supplemental_log_data_all FROM v$database;

LogMiner cannot work as an alternative to Auditing, LogMiner can only fetch the changes that happened to the database, unlike Auditing that can capture details like SELECT statements and login details, such information is not available in the Archivelogs to be mined.
Moreover, LogMiner can analyze only the available archive logs, it cannot go far in the past where the archive logs are not available, unless you restore the archivelogs from a backup media and feed them into the LogMiner.

To sum up. LogMiner is mainly used by the DBAs to recover logical data corruption, but we can use it as a forensic tool.

References:
https://oracle-base.com/articles/8i/logminer
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:433619656232

Sunday, December 18, 2016

Managed Recovery Stopped Applying Archives on Standby Database After Enabling Apply Delay on The Primary Archive Process

I had a requirement to set the archive apply delay feature between the primary and the standby DB, where the archives will be sent immediately to the standby site but will get applied in a delay of one hour. Such feature enables you to eliminate any erroneous update get executed on the primary from being applied immediately on the standby DB,which gives the DBA some time to think whether to fix the logical corruption or failing over to the standby DB.

Back to my story, On the primary DB, I've set the delay on the archival process that shift the archives to the standby DB to 60 minutes:

SQL> alter system set log_archive_dest_6='service="testprds"','LGWR ASYNC NOAFFIRM delay=60 optional compression=enable max_failure=0 max_connections=1 reopen=300 db_unique_name="testprds" net_timeout=90','valid_for=(all_logfiles,primary_role)';

Restart the archival process: [To let the changes take effect]
SQL> alter system set log_archive_dest_state_6=DEFER;
SQL> alter system set log_archive_dest_state_6=ENABLE;

Restart the recover on the standby which was already started in nodelay mode to start it in delayed mode:
SQL> recover managed standby database cancel;
SQL> recover managed standby database parallel 16 DISCONNECT;

Now I can see in the standby alertlog archives are getting received immediately on the standby and acknowledged with 60 minutes delay for apply:

Archived Log entry 378319 added for thread 1 sequence 681301 ID 0x6a29c0d6 dest 1:
ARCs: Archive log thread 1 sequence 681301 available in 60 minute(s)

Up to here, so far so good, it works as expected, but the horror didn't start yet :-) 
After 1 hour. There are no archives applied on the standby DB.
After 2 hours. Same situation, non of archives applied since I modified the archive process with delay option ! Something went wrong.

Looks easy! Not a rocket science, stop/start the recovery process with NODELAY option and the recovery will start overriding the delay:

SQL> RECOVER MANAGED STANDBY DATABASE cancel;
Media recovery complete.

SQL> RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE NODELAY DISCONNECT;
ORA-01153: an incompatible media recovery is active

WHAT?! It was just successfully cancelled !

Hopelessly I tried the following troubleshooting techniques, but non of them work:
- Repeating same above commands didn't help. 
- No MRP process was found on OS side to kill.
- Altered the delay parameter back to 0 and restart (defer/enable) the archive process on the primary DB didn't help.
- Killing ora_arc* processes on the standby didn't help.
- Deleting the archives that were marked with 60 minutes delay on standby, and recopy those archives again from primary site did help as well.

Now, it's time to think logically. This error means that although you've cancelled the media recovery there is still another media recovery processes is running didn't get cancelled when you executed "recover managed standby database cancel" command. Is this can happen? Yes it can happen !

So, we need to look into all recovery processes and their statuses:

SQL> select PROCESS,PID,STATUS from v$managed_standby;

PROCESS PID         STATUS
-------------   ----------  ------------
RFS            1292        IDLE
RFS            1095        IDLE
MR(fg)          19509       WAIT_FOR_LOG

Yes !, it's the one showing WAIT_FOR_LOG status.

Killed it from Linux shell using its PID:

# kill -9 19509

Now, starting media recovery on standby DB with NODELAY option:

SQL> RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE NODELAY DISCONNECT;
Media recovery complete.

Phew, finally the managed recovery is running!

Someone may ask why you didn't just restart the standby DB instance and it will force clear any stale recovery process?
The answer, imagine you are using your standby DB in Active Data Guard mode to support read only/reporting applications, restarting the standby database will impact the availability of those applications.

Thursday, August 28, 2014

Recover Data from Damaged/Corrupted/Inconsistent/Offline/Truncated/Dropped Table While the Database is Offline

When it comes to catastrophic scenarios where the database has been corrupted and there is no available backup, flashback feature was not turned on, database cannot be open due to errors or inconsistent status, there is still a chance to recover and save the data as long as the datafiles are still exist on the filesystem, using third party tools that able to directly read the datafiles without the need to open the database.
In this post I'll cover one of these tools, Oracle Database Unloader (ODU).

Download ODU:
===============
http://www.oracleodu.com/en/download

Full version:  Restores all data only if you got the license file from the support [Not Free].

Trial version: No license required, but it comes with limitations, it doesn't work on ASM, it can only recovers data under system datafiles
                     but for other datafiles it will recover it partially [less than 1% of all rows].
Note: In this demonstration I've used the TRIAL version to restore the data, this is why I was restoring tables located under system tablespace only to be able to recover all data.

Install ODU:
===========
Upload the downloaded file to the server and extract it:

# gunzip odu_433_linux_x86.tar.gz 
# tar xvf odu_433_linux_x86.tar 
# cd odu
# chown u+x odu

Prepare ODU:
=============
Before start using ODU for recovering data, first you have to modify the configuration files to point to the right paths of your datafiles.

In case the datafiles stored on ASM modify asmdisk.txt by adding ASM disks path to asmdisk.txt file:

# vi asmdisk.txt
0 /oradata/asm/disk1.dbf 
0 /oradata/asm/disk2.dbf 
0 /oradata/asm/disk3.dbf 

Note: The first column should be filled with 0

In case the datafiles are not stored on ASM, add all datafiles path to control.txt file:
This SQL can help you generate the lines you will add to control.txt :
SQL> SELECT 0,0,0,NAME FROM V$DATAFILE ORDER BY FILE#;

# vi control.txt
0 0 0 /database/orcl/datafiles/system01.dbf
0 0 0 /database/orcl/datafiles/users01.dbf
0 0 0 /database/orcl/datafiles/sysaux01.dbf
0 0 0 /database/orcl/datafiles/undotbs01.dbf
0 0 0 /database/orcl/datafiles/example01.dbf

Note:  The first three columns should be filled with 0 and the first line must point to the system datafile.

The rest of ODU configuration files like config.txt, contains configurations like block_size which must be the same for the database you are extracting data from.
In config.txt the parameter output_format represent the fashion where the data will be saved after extracting (unload) it from tables. the default value is text where data will be saved in SQL LOADER format, the other value is dump where data will be saved in export dump format.

In case you are using the full version, arrange for the license by save the configuration in a file and send it to ODU support to send you back the license file:
Save the controls to oductl.txt:
# ./odu
ODU> save control 
ODU> exit 

Send the file oductl.txt to the ODU support mailbox in http://www.oracleodu.com/en/support, they will send you the license file named oductl.dat, copy it under ODU installation directory.

* Note that Trial version doesn't need a license but it's data restoration capability is limited as mentioned before.

Using ODU:
============

Explore the database:
>>>>>>>>>>>>>>>>>
# ./odu

ODU> load config [filename]  #Not mandatory, if file name ommited it will load config.txt file
ODU> open [filename]  #Not mandatory, if file name ommited it will load control.txt file

ODU> unload dict                 #Load dictionary data from SYSTEM tablespace.
ODU> list user                     #List database users & roles.
ODU> desc scott.emp #descripe a table plus provide extra data like object id, tablespace,..etc.

Restore data of a truncated table: [TEST SCENARIO]
>>>>>>>>>>>>>>>>>>>>>>>>>>
*control.txt file must be loaded with datafiles location as shown before.

SQL> CREATE TABLE SCOTT.TEST TABLESPACE USERS AS SELECT * FROM DBA_USERS;
SQL> SELECT COUNT(*) FROM SCOTT.TEST;
SQL> TRUNCATE TABLE SCOTT.TEST;
SQL> alter system checkpoint;
SQL> alter tablespace users offline; #important/not mandatory to offline the tablespace of truncated table to avoid space overwrite.
SQL> alter system checkpoint; #important to write changes to the disk or ODU may not find the table.

# ./odu

ODU> load config [filename]  #Not mandatory, if file name ommited it will load config.txt file
ODU> open [filename]  #Not mandatory, if file name ommited it will load control.txt file

ODU> unload dict                 #Load dictionary data from SYSTEM tablespace.
ODU> desc scott.test #Get information of truncated table location (check it's tablespace number: TS#=<> )
Storage(Obj#=55125 DataObj#=55126 TS#=4 File#=4 Block#=403 Cluster=0)

ODU> scan extent tablespace 4 #Scan the extents of the tablespace # of the truncated table.
ODU> unload table scott.test object truncate #Restore all rows of truncated table in a SQL LOADER file.

SQL> alter tablespace users online; #Online back the tablespace of truncated to load back truncated data.

Use SQL LOADER to load the data back to the truncated table:
# cd /data
# sqlldr scott/tiger control=SCOTT_TEST.ctl 

Note: In case you want to restore the full data of a table not located on system tablespace, you must use the full licensed version of ODU.

Restore a Dropped Table:
>>>>>>>>>>>>>>>>>>>>>>
Let's create and drop a table for the following testing scenario:

SQL> create table scott.bb tablespace system as select * from dba_synonyms;
SQL> drop table scott.bb;
SQL> alter system checkpoint;

Before using ODU the first step to do is to get the OBJECT ID for the dropped table using Log Miner feature in Oracle:
--Add redo/archive that contain the drop transaction:
SQL> EXEC sys.dbms_logmnr.add_logfile(logfilename=>'/u01/app/oracle/oradata/xty/redo03.log'); 
--Start mining the registered archives:
SQL> EXEC sys.dbms_logmnr.start_logmnr(options=>sys.dbms_logmnr.dict_from_online_catalog);    
--Get the execution time of the drop statement:
SQL> SELECT scn,timestamp,sql_redo from v$logmnr_contents where operation='DDL' and sql_redo like '%drop%' order by 2 ; 
--Use the previous execution time to get the internal delete statement of the dropped table from the dictionary, where the object id can be found:
SQL> SELECT scn,timestamp,sql_redo from v$logmnr_contents
     where timestamp=to_date('2014-05-21 10:33:33','yyyy-mm-dd hh24:mi:ss') and sql_redo like '%OBJ%' order by 1;
--End the Log Miner task:
SQL> EXEC sys.dbms_logmnr.end_logmnr; 

Now we suppose to have the object id of the dropped table.

In case you're just testing and don't want to go through this leghnthy process before dropping the table just get it's object id from ODU:
# ./odu
ODU> unload dict
ODU> desc scott.bb                                      #You'll find the object id in the output.

Start the table restoration job using ODU:
# ./odu
ODU> unload dict #Load dictionary data from SYSTEM tablespace.
ODU> scan extent tablespace 0 #scan extents of tablespace 0 (system)
ODU> unload object all tablespace 0 sample  #Provides you with the command you shall use to restore for EVERY object inside tablespace 0
ODU> unload user scott #Unload ALL tables owned by user SCOTT (each table will have 3 files located under data directory).
ODU> unload object 55150 sample  #Provides you with the command you shall use to restore, for SPECIFIC object inside tablespace 0.
ODU> unload object 55150 tablespace 0 column VARCHAR2 VARCHAR2 VARCHAR2 VARCHAR2 #Just copy & execute the command from the previous output.

Note: unload command will generate three files for each unloaded table ".txt",".sql",".ctl"

From OS side modify file /data/ODU_0000055150.sql> by changing the name of object from ODU_0000055150 to SCOTT.BB and execute it:
SQL> @/data/ODU_0000055150.sql>
Table created.

From OS modify file /data/ODU_0000055150.ctl> change the name of object from ODU_0000055150 to SCOTT.BB and start load the table with data using SQLLOADER:
# sqlldr scott/tiger control=ODU_0000055150.ctl


Export Data from an existing Table: [Database is down / Skipping Auditing/Permissions]
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# ./odu
ODU> unload dict #Load dictionary data from SYSTEM tablespace.
ODU> desc scott.bb #Scan the extents of the tablespace number of the truncated table.
ODU> scan extent tablespace 0 #Scan the extents of the tablespace #.
ODU> unload table scott.bb object scanned #Export data of table SYS.AA into SQL LOADER file. [object scanned] is not mandatory.

Open the .sql file and change the table owner&name if you wish, do the same for .ctl file then use SQLLOADER to import the data.
# sqlldr scott/tiger control=SCOTT_AA.ctl


Conclusion:
ODU tool can help restore data in catastrophic scenarios where there is no available backup or the database has damaged or cannot be opened for any reason.
On the other hand this tool proves that database security can be easily compromised by anyone have access to database files and have a basic knowledge in using such tools, in order to avoid such breach the DBA should consider encrypting data on filesystem level using encryption features such as TDE, also security hardening and limiting the access on the filesystem level should be highly considered to prevent un-authorized users from accessing database files.

REFERENCES:
Full documentation: http://www.oracleodu.com/soft/ODUUserGuide_en.pdf
Real success story:     http://www.basecare.dk/6storage/983/7/recovering_lob-index_block_corruption.pdf

Sunday, November 13, 2011

Switch Database to RMAN Image Copy Backup and Switch It Back

Considering RMAN image copy backup for your production databases in your backup strategy is a very helpful approach to easily recover from disk failure scenarios.

Why consider RMAN image backups in your backup strategy:
It helps you fix disastrous situations due to physical damages, such as losing one or more datafiles in a minimal downtime, by eliminating the need for restoring the datafile from scratch.
Note: You should keep a recent RMAN image copy on the disk all the time to help minimize the downtime during physical damage situations.
This script can help you out in creating RMAN image copy backup:
https://www.dropbox.com/s/umempc88srgr8k8/schedule_rman_image_copy_bkp.sh?dl=0
You have to change the variables inside the script as instructed by the comments to match your environment.

When to switch to the image backup:
1-You lost a disk contains one datafile or more.
2-A datafile has been deleted or mistakenly overwritten on the OS level.
3-A datafile has a wide physical corruption.
4-You want to move your database datafiles to a different location on the same server with a minimal downtime.

In this demonstration I'll go through two real-world scenarios:
Scenario #1 is related to switching the whole database to the RMAN image backup.
Scenario #2 is for switching one datafile only to the RMAN image backup.

Scenario #1 Switching the whole database to "RMAN copy backup": [on Windows Platform]

> For this scenario, let's take first an RMAN backup as copy for the whole database:
RMAN> backup as copy database;

For more information about Why to consider RMAN image copy, How to use it and How to recover an RMAN image copy from incremental backup, visit this link:
http://dba-thoughts.blogspot.ae/2014/08/recover-rman-image-copy-backup.html

Now let's suppose you lost your datafiles and want to switch to the RMAN image copy:
>Re-bounce the database to the mount mode:
RMAN> shutdown immediate;
RMAN> startup mount;

Switch the database to the image copy backup:
Connect to the RMAN:
# rman target /
RMAN> switch database to copy;
datafile 1 switched to datafile copy "D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSTEM_7BZ8WZ7S_.DBF"
datafile 2 switched to datafile copy "D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSAUX_7BZ8ZZD8_.DBF"
datafile 3 switched to datafile copy "D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_UNDOTBS1_7BZ93XM6_.DBF"
datafile 4 switched to datafile copy "D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_USERS_7BZ963HC_.DBF"
datafile 5 switched to datafile copy "D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_EXAMPLE_7BZ95N9Z_.DBF"

RMAN> recover database;

This will recover the database until the time of failure, this means that the archives from the time of last RMAN copy backup till the time of the failure must exist.
e.g. If the RMAN copy backup has been taken at 1:00am and the failure happen at 9:00am the archives between 1:00 to 9:00am should be available plus the current database redologs.

RMAN> alter database open;

The case of Incomplete Recovery:
In case you lost one or more archive without having a backup for them, you will be forced to recover to the last available archive using:
SQL> RECOVER DATABASE UNTIL CANCEL;
AUTO
Or from RMAN:
RMAN> run{
                    set until sequence xxx;   # Where xxx is the last higher available archivelog sequence.
                     recover database;
                     alter database open resetlogs;
                    }

>Datafiles are now pointing to the RMAN copy backup:
SQL> select file_name from dba_data_files;
FILE_NAME
-----------------------------------------------------------------------------------------------------
D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_USERS_7BZK07B2_.DBF
D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_UNDOTBS1_7BZJYX5S_.DBF
D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSAUX_7BZJWLDN_.DBF
D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSTEM_7BZJT6RF_.DBF
D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_EXAMPLE_7BZK000C_.DBF

Note: Temporary tempfiles will not be switched to the image backup as they been never considered during the backup time, so you just need to re-add them back.

Switch back the database to its original files: [In case you managed to restore back the original datafiles]

Datafiles should exist in their original places, whatever was the way you used to copy them back.
You have to use "rename file" command for each datafile to point them to the original location:

>First you have to restart the database to the mount mode:
SQL> shu immediate;
SQL> startup mount;

>Second run the rename command to point to datafile in the original location:
SQL> alter database rename file 'D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_USERS_7BZK07B2_.DBF' to 'D:\ora11g\oradata\orcl\USERS01.dbf';

SQL> alter database rename file 'D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_UNDOTBS1_7BZJYX5S_.DBF' to 
'D:\ora11g\oradata\orcl\UNDOTBS01.dbf';

SQL> alter database rename file 'D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSAUX_7BZJWLDN_.DBF' to 
'D:\ora11g\oradata\orcl\SYSAUX01.dbf';

SQL> alter database rename file 'D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_SYSTEM_7BZJT6RF_.DBF' to 
'D:\ora11g\oradata\orcl\SYSTEM01.dbf';

SQL> alter database rename file 'D:\ORA11G\FLASH_RECOVERY_AREA\ORCL\DATAFILE\O1_MF_EXAMPLE_7BZK000C_.DBF' to 
'D:\ora11g\oradata\orcl\EXAMPLE01.dbf';

Note: You can re-create the controlfile pointing the datafiles to their old location instead of renaming the datafiles one by one.

Recover the database:
RMAN> recover database;

Open the database:
RMAN> alter database open;

Now the datafiles are pointing to its original location:
SQL> select file_name from dba_data_files;

FILE_NAME
-------------------------------------------
D:\ORA11G\ORADATA\ORCL\USERS01.DBF
D:\ORA11G\ORADATA\ORCL\UNDOTBS01.DBF
D:\ORA11G\ORADATA\ORCL\SYSAUX01.DBF
D:\ORA11G\ORADATA\ORCL\SYSTEM01.DBF
D:\ORA11G\ORADATA\ORCL\EXAMPLE01.DBF


Scenario #2 Switch one datafile to the RMAN copy backup: [on Linux Platform]

The same practice you can do if you lost one or more datafiles:

Let's suppose we lost datafile# 4 which is part of tablespace users and you're getting this error in the alertlog:

ORA-01116: error in opening database file 4
ORA-01110: data file 4: '/ora_dev1/LEGACY/datafiles/users01.dbf'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory

If you have a recent RMAN "backup as copy" for the database or for the lost datafile, you can switch that datafile to its copy piece in the image backup, by doing the following:

>Take the lost datafile offline:
# rman target /
RMAN> sql 'alter database datafile 4 offline';

>Switch the datafile to it's Copy backup piece:
RMAN> switch datafile 4 to copy;

Datafile 4 switched to datafile copy "/ora_dev1/LEGACY/copy/LEGACY_766159463_55"

>Recover the datafile:
RMAN> recover datafile 4;

>Bring back the datafile online:
RMAN> sql 'alter database datafile 4 online';

Now the datafile location will point to the rman "copy backup" piece:

SQL> select tablespace_name,file_name,status from dba_data_files where file_id=4;

TABLESPACE_NAM FILE_NAME STATUS
-------------- -------------------------------------------- ---------
USERS /ora_dev1/LEGACY/copy/LEGACY_766159463_55 AVAILABLE

DONE.

Note: For the above scenario to succeed, all archive logs from the time of image copy backup creation until the time of failure should exist.