Skip to main content

How to configure Logical Standby


  • Introduction: Oracle Logical Standby database is a type of standby database created and maintained using Oracle Data Guard (ODG), where redo data from the primary database is transformed into SQL statements and applied logically on the standby database. A Logical Standby differs from a Physical Standby by applying redo through SQL apply rather than block-level recovery. This approach keeps the standby database open in read/write mode while it remains in sync with the primary. Logical Standby databases are particularly well suited for environments that require both robust data protection and effective workload offloading, without adding performance overhead to the primary system. In this article, we will configure the Logical Standby.

  • Prerequisites:
    • Oracle Data Guard (Physical Standby) should be configured. For Physical Standby Setup Click here.
    • Verify the unsupported objects for Logical Standby.
    • Tables must have a primary key or unique constraint.

  • Environment:
  • Server Primary Standby
    Hostname dcdb.oraeasy.com drdb.oraeasy.com
    OS OEL 9 OEL 9
    SID ORADBDC ORADBDR
    Service Name ORADBDC ORADBDR

  • Now proceed to convert Physical Standby into Logical Standby Database activity step by step:

1. Check the sync between Primary and Standby.

==> Primary:
SQL> select NAME, DB_UNIQUE_NAME, DATABASE_ROLE, OPEN_MODE from v$database; NAME DB_UNIQUE_NAME DATABASE_ROLE OPEN_MODE --------- ------------------------------ ---------------- -------------------- ORADB oradbdc PRIMARY READ WRITE SQL> set lines 200 pages 300 SQL> alter session set nls_date_format= 'DD-MON-YYYY HH24:MI:SS'; SQL> select d.db_unique_name,d.database_role, a.thread#, b.last_seq, a.applied_seq, a.last_app_timestamp, b.last_seq - a.applied_seq ARC_DIFF FROM (select thread#, MAX(sequence#) applied_seq, MAX(next_time) last_app_timestamp from gv$archived_log where applied='YES' group by thread#) a, (select thread#, MAX(sequence#) last_seq from gv$archived_log group by thread#) b, (select db_unique_name,database_role from v$database) d where a.thread#=b.thread#; Session altered. DB_UNIQUE_NAME DATABASE_ROLE THREAD# LAST_SEQ APPLIED_SEQ LAST_APP_TIMESTAMP ARC_DIFF ------------------------------ ---------------- ---------- ---------- ----------- -------------------- ---------- oradbdc PRIMARY 1 36 36 17-DEC-2025 20:53:18 0 ==> Standby:
SQL> select NAME, DB_UNIQUE_NAME, DATABASE_ROLE, OPEN_MODE from v$database; NAME DB_UNIQUE_NAME DATABASE_ROLE OPEN_MODE --------- ------------------------------ ---------------- -------------------- ORADB oradbdr PHYSICAL STANDBY MOUNTED SQL> set lines 200 pages 300 SQL> alter session set nls_date_format= 'DD-MON-YYYY HH24:MI:SS'; SQL> select d.db_unique_name,d.database_role, a.thread#, b.last_seq, a.applied_seq, b.last_seq - a.applied_seq ARC_DIFF, a.last_app_timestamp, round((sysdate - a.last_app_timestamp)*24*60,2) Gap_in_Mins, round((sysdate - a.last_app_timestamp)*24*60*60,2) Gap_in_Seconds FROM (select thread#, MAX(sequence#) applied_seq, MAX(next_time) last_app_timestamp from gv$archived_log where REGISTRAR='RFS' and applied='YES' group by thread#) a, (select thread#, MAX(sequence#) last_seq from gv$archived_log group by thread#) b, (select db_unique_name,database_role from v$database) d where a.thread#=b.thread#; Session altered. DB_UNIQUE_NAME DATABASE_ROLE THREAD# LAST_SEQ APPLIED_SEQ ARC_DIFF LAST_APP_TIMESTAMP GAP_IN_MINS GAP_IN_SECONDS ------------------------------ ---------------- ---------- ---------- ----------- ---------- -------------------- ----------- -------------- oradbdr PHYSICAL STANDBY 1 36 36 0 17-DEC-2025 20:53:18 3.85 231
2. Now on Primary, check the unsupported objects for Logical Standby.

==> Unsupported Objects
SQL> col owner for a15; SQL> select distinct owner, table_name from dba_logstdby_unsupported order by owner,table_name;SQL> no rows selected ==> Crosscheck for Primary Key. Add a primary key in table if any record return below.
SQL> SELECT OWNER, TABLE_NAME,BAD_COLUMN FROM DBA_LOGSTDBY_NOT_UNIQUE; no rows selected
3. Now on Standby stop the MRP.

SQL> ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;

Database altered.

4. Now create directory for LOG_ARCHIVE_DEST_3 and set this parameter for Standby Logs in both Primary & Standby. Also set LOG_ARCHIVE_DEST_1 on both if not set yet.

==> Primary:
[oracle@dcdb ~]$ mkdir -p /u01/app/oracle/arch/standby SQL> show parameter recovery NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_recovery_file_dest string /u01/app/oracle/fast_recovery_ area db_recovery_file_dest_size big integer 8G recovery_parallelism integer 0 remote_recovery_file_dest string SQL> show parameter LOG_ARCHIVE_DEST_1 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_1 string SQL> show parameter LOG_ARCHIVE_DEST_3 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_3 string SQL> ALTER SYSTEM SET LOG_ARCHIVE_DEST_1='LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ONLINE_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=oradbdc' scope=both; System altered. SQL> ALTER SYSTEM SET LOG_ARCHIVE_DEST_3='LOCATION=/u01/app/oracle/arch/standby VALID_FOR=(STANDBY_LOGFILES,STANDBY_ROLE) DB_UNIQUE_NAME=oradbdc' scope=both; System altered. SQL> show parameter LOG_ARCHIVE_DEST_1 NAME TYPE VALUE ------------------------------------ ----------- ----------------------------------- log_archive_dest_1 string LOCATION=USE_DB_RECOVERY_FILE_ DEST VALID_FOR=(ONLINE_LOGFILE S,ALL_ROLES) DB_UNIQUE_NAME=oradbdc SQL> show parameter LOG_ARCHIVE_DEST_3 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_3 string LOCATION=/u01/app/oracle/arch/ standby VALID_FOR=(STANDBY_LOG FILES,STANDBY_ROLE) DB_UNIQUE_NAME=oradbdc ==> Standby:
[oracle@drdb ~]$ mkdir -p /u01/app/oracle/arch/standby SQL> show parameter recovery NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_recovery_file_dest string /u01/app/oracle/fast_recovery_ area db_recovery_file_dest_size big integer 8G recovery_parallelism integer 0 remote_recovery_file_dest string SQL> show parameter LOG_ARCHIVE_DEST_1 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_1 string SQL> show parameter LOG_ARCHIVE_DEST_3 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_3 string SQL> ALTER SYSTEM SET LOG_ARCHIVE_DEST_1='LOCATION=USE_DB_RECOVERY_FILE_DEST VALID_FOR=(ONLINE_LOGFILES,ALL_ROLES) DB_UNIQUE_NAME=oradbdr' scope=both; System altered. SQL> ALTER SYSTEM SET LOG_ARCHIVE_DEST_3='LOCATION=/u01/app/oracle/arch/standby VALID_FOR=(STANDBY_LOGFILES,STANDBY_ROLE) DB_UNIQUE_NAME=oradbdr' scope=both; System altered. SQL> show parameter LOG_ARCHIVE_DEST_1 NAME TYPE VALUE ------------------------------------ ----------- ----------------------------------- log_archive_dest_1 string LOCATION=USE_DB_RECOVERY_FILE_ DEST VALID_FOR=(ONLINE_LOGFILE S,ALL_ROLES) DB_UNIQUE_NAME=oradbdr SQL> show parameter LOG_ARCHIVE_DEST_3 NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ log_archive_dest_3 string LOCATION=/u01/app/oracle/arch/ standby VALID_FOR=(STANDBY_LOG FILES,STANDBY_ROLE) DB_UNIQUE_NAME=oradbdr
5. Now execute this on the primary database to build a dictionary in the redo data.

SQL> EXECUTE DBMS_LOGSTDBY.BUILD;

PL/SQL procedure successfully completed.

6. Now on standby run below to convert in Logical Standby.

Syntax: ALTER DATABASE RECOVER TO LOGICAL STANDBY db_name;
For db_name value, specify a database name that is different from the primary database to identify the new logical standby database. Also make sure the database is running with spfile.

SQL> alter database recover to logical standby drdb;

Database altered.

SQL> shutdown immediate
ORA-01507: database not mounted


ORACLE instance shut down.
SQL>
SQL> startup mount
ORACLE instance started.

Total System Global Area 1073739904 bytes
Fixed Size                  8947840 bytes
Variable Size             897581056 bytes
Database Buffers          159383552 bytes
Redo Buffers                7827456 bytes
Database mounted.
SQL>
SQL> alter database open resetlogs;

Database altered.

SQL> select name, open_mode, db_unique_name, database_role, guard_status from v$database;

NAME      OPEN_MODE            DB_UNIQUE_NAME    DATABASE_ROLE    GUARD_STATUS
--------- -------------------- ----------------  ---------------- ------------
DRDB      READ WRITE           oradbdr           LOGICAL STANDBY  ALL

SQL> show parameter db_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_name                              string      DRDB
SQL>

7. Now start the logical apply at standby and verify.

SQL> alter database start logical standby apply immediate;

Database altered.

SQL>  SELECT name, value FROM v$logstdby_stats;

NAME                                            VALUE
----------------------------------------------  -----------------
logminer session id                             1
number of preparers                             1
number of appliers                              5
server processes in use                         9
maximum SGA for LCR cache (MB)                  100
preserve commit order                           TRUE
transaction consistency                         FULL
parallel read enabled                           N
coordinator state                               INITIALIZING
coordinator startup time                        17-DEC-25
coordinator uptime (seconds)                    5
txns received from logminer                     0
txns assigned to apply                          0
txns applied                                    0
txns discarded during restart                   0
large txns waiting to be assigned               0
session restart SCN                             0
rolled back txns mined                          0
DDL txns mined                                  0
CTAS txns mined                                 0
bytes of redo mined                             0
bytes paged out                                 0
pageout time (seconds)                          0
bytes checkpointed                              0
checkpoint time (seconds)                       0
system idle time (seconds)                      0
standby redo logs mined                         0
archived logs mined                             0
gap fetched logs mined                          0
standby redo log reuse detected                 0
logfile open failures                           0
current logfile wait (seconds)                  0
total logfile wait (seconds)                    0
thread enable mined                             0
thread disable mined                            0
distinct txns in queue                          0
number of logged PLSQL procedures mined         0
waits due to full transaction queue             0
reque due to full transaction queue             0
resize due to full transaction queue            0
waits due to full redo queue                    0
waits due to full merge queue                   0
work units partial                              0
work units empty                                0
transaction start skipped                       0
number of redo records decrypted                0

46 rows selected.

SQL> !ps -ef|grep lsp
oracle      7371       1  0 21:08 ?        00:00:00 ora_lsp0_oradbdr
oracle      7400    5861  0 21:10 pts/0    00:00:00 /bin/bash -c ps -ef|grep lsp
oracle      7402    7400  0 21:10 pts/0    00:00:00 grep lsp

8. Now do some log switches on Primary and check sync.

==> Primary:
SQL> alter system switch logfile; System altered. SQL> / System altered. SQL> select max(sequence#) from v$archived_log; MAX(SEQUENCE#) -------------- 42 ==> Standby:
SQL> select sequence#,first_change#,next_change#,timestamp,applied,blocks,block_size from dba_logstdby_log; SEQUENCE# FIRST_CHANGE# NEXT_CHANGE# TIMESTAMP APPLIED BLOCKS BLOCK_SIZE ---------- ------------- ------------ -------------------- -------- ---------- ---------- 38 2354571 2355105 17-DEC-2025 21:11:11 YES 38359 512 39 2355105 2355125 17-DEC-2025 21:11:10 YES 40 512 40 2355125 2358171 17-DEC-2025 21:19:34 YES 2685 512 41 2358171 2358214 17-DEC-2025 21:19:53 YES 22 512 42 2358214 2358226 17-DEC-2025 21:19:57 CURRENT 20 512 SQL> SQL> COL NAME FOR A20 SQL> COL VALUE FOR A12 SQL> COL UNIT FOR A30 SQL> SELECT NAME, VALUE, UNIT FROM V$DATAGUARD_STATS; NAME VALUE UNIT -------------------- ------------ ------------------------------ transport lag +00 00:00:14 day(2) to second(0) interval apply lag +00 00:00:14 day(2) to second(0) interval apply finish time +00 00:00:00 day(2) to second(3) interval estimated startup time 24 second SQL> select a.latest_scn,a.latest_time,a.applied_scn,a.applied_time,b.read_scn,b.read_time from v$logstdby_progress a,dba_logstdby_progress b; LATEST_SCN LATEST_TIME APPLIED_SCN APPLIED_TIME READ_SCN READ_TIME ---------- -------------------- ----------- -------------------- ---------- -------------------- 2358480 17-DEC-2025 21:21:51 2358480 17-DEC-2025 21:21:51 2358481 17-DEC-2025 21:21:51
9. Now create user on Primary and check on Standby.

==> Primary:
SQL> def DEFINE _DATE = "18-DEC-25" (CHAR) DEFINE _CONNECT_IDENTIFIER = "oradbdc" (CHAR) DEFINE _USER = "SYS" (CHAR) DEFINE _PRIVILEGE = "AS SYSDBA" (CHAR) DEFINE _SQLPLUS_RELEASE = "1928000000" (CHAR) DEFINE _EDITOR = "vi" (CHAR) DEFINE _O_VERSION = "Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.28.0.0.0" (CHAR) DEFINE _O_RELEASE = "1928000000" (CHAR) SQL> SQL> select username from dba_users where username='TEST'; no rows selected SQL> create user test identified by Test##123; User created. SQL> select username from dba_users where username='TEST'; USERNAME -------------- TEST SQL> alter system switch logfile; System altered. ==> Standby:
SQL> def DEFINE _DATE = "18-DEC-25" (CHAR) DEFINE _CONNECT_IDENTIFIER = "oradbdr" (CHAR) DEFINE _USER = "SYS" (CHAR) DEFINE _PRIVILEGE = "AS SYSDBA" (CHAR) DEFINE _SQLPLUS_RELEASE = "1928000000" (CHAR) DEFINE _EDITOR = "vi" (CHAR) DEFINE _O_VERSION = "Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.28.0.0.0" (CHAR) DEFINE _O_RELEASE = "1928000000" (CHAR) DEFINE _RC = "0" (CHAR) SQL> select username from dba_users where username='TEST'; USERNAME -------------- TEST


Thank you for reading!

I hope this content has been helpful to you. Your feedback and suggestions are always welcome — feel free to leave a comment or reach out with any queries.

Abhishek Shrivastava

📧 Email: oraeasyy@gmail.com
🌐 Website: www.oraeasy.com

Follow Us

Comments