DML over Standby for Active Data Guard in 19c

With the new 19c version the Data Guard received some attention and now we can do DML over the standby and it will be redirect to primary database. It is not hard to implement, but unfortunately there is no much information about that in the docs about that.

As training exercise I tested this new feature and want to share some information about that. First, the environment that I used (and the requirements too):

  • Primary and Standby databases running 19c.
  • Data Guard in Maximum Availability .
  • Active Data Guard enabled.

Remember that the idea of DML over the standby it is to use in some cases where your reporting application need to update some tables and few records (like audit logins) while processing the data in the standby. The volume of DML is (and will be) low. At this point there is no effort to allow, or create, a multiple active-active datacenters/sites for your database. If you start to execute a lot of DML in the standby side you can impact the primary database and you adding the fact that you can maximize the problems for locks and concurrency.

Theorical info about DDL in the standby you can find here and here.  The basic workflow you can see below:

To create the DG configuration you can follow the steps that are available in the internet in many sources, one good example it is here. If you want to see what I made, you can check this file that contains the output from the steps for my config “Log-Create-PHY-STB-19c”.

Before continue, since here we have a lot of interaction between primary and standby, I put all the code that I executed over primary identified with PRIMARY, and executed as standby with the STANDBY in the codes below.

The first step to configure is guarantee that everything is ok with the config for DG. The main thing is check the “Real Time Query” and ON:

DGMGRL> show configuration

Configuration - dg19b2

  Protection Mode: MaxAvailability
  Members:
  dg19  - Primary database
    dg19s - Physical standby database

Fast-Start Failover:  Disabled

Configuration Status:
SUCCESS   (status updated 31 seconds ago)

DGMGRL> show database dg19s

Database - dg19s

  Role:               PHYSICAL STANDBY
  Intended State:     APPLY-ON
  Transport Lag:      0 seconds (computed 0 seconds ago)
  Apply Lag:          0 seconds (computed 0 seconds ago)
  Average Apply Rate: 2.00 KByte/s
  Real Time Query:    ON
  Instance(s):
    dg19s

Database Status:
SUCCESS

DGMGRL>

After that you enable the parameter ADG_REDIRECT_DML  in both sides. Primary:

[oracle@orcl19p ~]$ sqlplus sys/oracle@dg19 as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 17:50:38 2019
Version 19.2.0.0.0

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


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> show parameter adg_redirect_dml

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
adg_redirect_dml                     boolean     FALSE
SQL> 
SQL> alter system set adg_redirect_dml = true scope = both sid = '*';

System altered.

SQL>

And in standby:

[oracle@orcl19s ~]$ sqlplus sys/oracle@dg19s as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 17:51:19 2019
Version 19.2.0.0.0

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


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> select open_mode from v$database;

OPEN_MODE
--------------------
READ ONLY WITH APPLY

SQL> show parameter adg_redirect_dml

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
adg_redirect_dml                     boolean     FALSE
SQL>
SQL> alter system set adg_redirect_dml = true scope = both sid = '*';

System altered.

SQL>

To test the DML redirection I created one table with some data in the primary database. Here I amd connected as SYS and without PDB, after I will do the same for PDB and with a normal user:

SQL> CREATE TABLE testDML(c1 NUMBER, c2 VARCHAR2(50), c3 DATE) TABLESPACE users;

Table created.

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);

1 row created.

SQL> commit;

Commit complete.

SQL> select c1, TO_CHAR(c3, 'DD/MM/RRRR HH24:MI') FROM testDML;

        C1 TO_CHAR(C3,'DD/M
---------- ----------------
         1 14/04/2019 17:58

SQL>

In the standby you can see the table and do some DML over it:

SQL> select c1, TO_CHAR(c3, 'DD/MM/RRRR HH24:MI') FROM testDML;

        C1 TO_CHAR(C3,'DD/M
---------- ----------------
         1 14/04/2019 17:58

SQL> select database_role from v$database;

DATABASE_ROLE
----------------
PHYSICAL STANDBY

SQL> delete from testDML;

1 row deleted.

SQL> commit;

Commit complete.

SQL>

And in the primary, you can see that the data was deleted:

SQL> select c1, TO_CHAR(c3, 'DD/MM/RRRR HH24:MI') FROM testDML;

no rows selected

SQL> SELECT TO_CHAR(sysdate, 'DD/MM/RRRR HH24:MI') FROM dual;

TO_CHAR(SYSDATE,
----------------
14/04/2019 18:01

SQL>

Locks

But in the case of locks, how it works? Some new event? Some different? To simulate this I inserted the data in the primary again:

SQL> select c1, TO_CHAR(c3, 'DD/MM/RRRR HH24:MI') as c3 FROM testDML;

        C1 C3
---------- ----------------
         1 14/04/2019 18:04

SQL>

In the primary I deleted all the data but without commit to lock all the table:

SQL> delete from testDML;

1 row deleted.

SQL>

After that I tried to update all the rows in the table. But, as expected, my session got stuck:

SQL> update testDML set c1 = 2;

At same time I went to production and check for locks and events:

SQL> SELECT username, final_blocking_session, final_blocking_instance, event lockwait, status, machine, service_name , sql_id FROM gv$session WHERE username IS NOT NULL;

USERNAME             FINAL_BLOCKING_SESSION FINAL_BLOCKING_INSTANCE LOCKWAIT                                                         STATUS   MACHINE              SERVICE_NAME                             SQL_ID
-------------------- ---------------------- ----------------------- ---------------------------------------------------------------- -------- -------------------- ---------------------------------------- -------------
SYS                                                                 OFS idle                                                         ACTIVE   orcl19p.oralocal     SYS$BACKGROUND
SYSRAC                                                              SQL*Net message from client                                      INACTIVE orcl19p.oralocal     SYS$USERS
PUBLIC                                                              SQL*Net message from client                                      INACTIVE orcl19s.oralocal     SYS$USERS
SYS                                                                 SQL*Net message to client                                        ACTIVE   orcl19p.oralocal     SYS$USERS                                4dzzccufjscvp
SYS                                      86                       1 enq: TX - row lock contention                                    ACTIVE   orcl19s.oralocal     SYS$USERS                                49b39kubnb2d1

SQL>
SQL> col sql_text format a50
SQL> select sql_text from v$sql where sql_id = '49b39kubnb2d1';

SQL_TEXT
--------------------------------------------------
UPDATE "TESTDML" "A1" SET "C1" = 2

SQL>

As you can see, there is no different event. The primary database reports TX coming from one connection of standby database. And you can even see the SQL. Normal behaviour.

But, after some time in lock state, the session in the standby reported a new error. ORA-02049: timeout: distributed transaction waiting for lock and ORA-02063: preceding line from ADGREDIRECT. These exists just to avoid eternal wait in the standby.

SQL> update testDML set c1 = 2;
update testDML set c1 = 2
*
ERROR at line 1:
ORA-02049: timeout: distributed transaction waiting for lock
ORA-02063: preceding line from ADGREDIRECT

SQL>

If you do in the opposite site, deleting the registry standby and not doing the commit. The session in the primary that it is waiting never receive the ORA-02049. And the same for the session in standby, it holds the lock until you commit/rollback.

Over PDB

If you want to do the same using PDB, you need to work a little with services to allow direct connections in the physical standby using the services. Basically you need to create the service and set the role (to start it) as physical_standby. The trick part here is that you need to, at least, call the start command in the primary because doing this you registry it in the service$ table. Since you are in the DG config and running the Active Data Guard (this is requirement because the redo is open with Real Time Query) you can open it in the standby too. I added this info here because with DML over standby this config start to be recurrent.

First, create and add the service in primary database:

[oracle@orcl19p ~]$ srvctl add service -db dg19 -service dgpdb_dml -pdb dgpdb -role physical_standby
[oracle@orcl19p ~]$ srvctl start service -db dg19 -service dgpdb_dml -pdb dgpdb
[oracle@orcl19p ~]$
[oracle@orcl19p ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 18:48:28 2019
Version 19.2.0.0.0

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


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> alter session set container = DGPDB;

Session altered.

SQL> set linesize 250
SQL> col NAME format a30
SQL> col NETWORK_NAME format a30
SQL> col pdb format a30
SQL> SELECT name, network_name, pdb FROM service$;

NAME                           NETWORK_NAME                   PDB
------------------------------ ------------------------------ ------------------------------
DGPDB                          DGPDB                          DGPDB
dgpdb_dml                      dgpdb_dml                      DGPDB

SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0
[oracle@orcl19p ~]$

After that you do the same in the standby:

[oracle@orcl19s ~]$ srvctl add service -db dg19s -service dgpdb_dml -pdb dgpdb -role physical_standby
[oracle@orcl19s ~]$ srvctl start service -db dg19s -service dgpdb_dml -pdb dgpdb
[oracle@orcl19s ~]$
[oracle@orcl19s ~]$ srvctl stop database -d dg19s
[oracle@orcl19s ~]$ srvctl start database -d dg19s
[oracle@orcl19s ~]$
[oracle@orcl19s ~]$ srvctl status service -d dg19s
Service dgpdb_dml is running
[oracle@orcl19s ~]$

Here something to add. I don’t know why but for me just worked (the service appears as enable to connect) after I restart the standby. Since it is not focus for this post this point, you can find good info here (original post from Ivica Arsov) and here.

After that you can test the same as before (create table in PDB and populate):

[oracle@orcl19p ~]$ sqlplus simon/simon@orcl19p/DGPDB

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 21:37:36 2019
Version 19.2.0.0.0

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

Last Successful login time: Sun Apr 14 2019 21:36:05 +02:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> CREATE TABLE testDML(c1 NUMBER, c2 VARCHAR2(50), c3 DATE) TABLESPACE users;

Table created.

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);

1 row created.

SQL> commit;

Commit complete.

SQL>

After that do some DML over standby:

[oracle@orcl19s ~]$ sqlplus simon/simon@orcl19s/DGPDB

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 21:38:17 2019
Version 19.2.0.0.0

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

Last Successful login time: Sun Apr 14 2019 21:37:36 +02:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL>
SQL> delete from testDML;

1 row deleted.

SQL>

And if you have some locks the behaviour it is the same than before. But now, with the information from user and con_id from connection:

SQL> select USERNAME, FINAL_BLOCKING_SESSION, FINAL_BLOCKING_INSTANCE, EVENT LOCKWAIT, STATUS, machine, service_name , sql_id, con_id from gv$session where username is not null;

USERNAME             FINAL_BLOCKING_SESSION FINAL_BLOCKING_INSTANCE LOCKWAIT                                                         STATUS   MACHINE              SERVICE_NAME         SQL_ID            CON_ID
-------------------- ---------------------- ----------------------- ---------------------------------------------------------------- -------- -------------------- -------------------- ------------- ----------
SYS                                                                 OFS idle                                                         ACTIVE   orcl19p.oralocal     SYS$BACKGROUND                              0
SYSRAC                                                              SQL*Net message from client                                      INACTIVE orcl19p.oralocal     SYS$USERS                                   1
SYSRAC                                                              SQL*Net message from client                                      INACTIVE orcl19p.oralocal     SYS$USERS                                   1
PUBLIC                                                              SQL*Net message from client                                      INACTIVE orcl19s.oralocal     SYS$USERS                                   1
SIMON                                   102                       1 enq: TX - row lock contention                                    ACTIVE   orcl19p.oralocal     dgpdb                4sqjaugwcxar0          3
SYS                                                                 SQL*Net message to client                                        ACTIVE   orcl19p.oralocal     SYS$USERS            cssctts2u81n4          1
SIMON                                                               SQL*Net message from client                                      INACTIVE orcl19s.oralocal     SYS$USERS                                   3

7 rows selected.

SQL>
SQL> select username, machine, service_name, program from gv$session where inst_id = 1 and sid = 102;

USERNAME             MACHINE              SERVICE_NAME         PROGRAM
-------------------- -------------------- -------------------- ------------------------------------------------
SIMON                orcl19s.oralocal     SYS$USERS            oracle@orcl19s.oralocal (TNS V1-V3)

SQL>

As you can see above the connection appear in the primary as a normal connection, nothing complicate or special to handle. And in the listener log the connection (coming from standby when receiving the DML) is normal:

2019-04-14T22:12:57.977135+02:00
14-APR-2019 22:12:57 * (CONNECT_DATA=(SERVICE_NAME=8106653b19ca6636e053016410ac3c21)(CID=(PROGRAM=oracle)(HOST=orcl19s.oralocal)(USER=oracle))) * (ADDRESS=(PROTOCOL=tcp)(HOST=172.16.200.1)(PORT=36782)) * establish * 8106653b19ca6636e053016410ac3c21 * 0
2019-04-14T22:14:02.121149+02:00
14-APR-2019 22:14:02 * (CONNECT_DATA=(SERVER=DEDICATED)(SID=dg19)(CID=(PROGRAM=oracle)(HOST=orcl19s.oralocal)(USER=oracle))) * (ADDRESS=(PROTOCOL=tcp)(HOST=172.16.200.1)(PORT=36786)) * establish * dg19 * 0

The behaviour it is so normal that you will see the cursors open in the primary (the lock system is by row):

SQL> create index ixtestDML on testDML(c1) tablespace users;

Index created.

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);

1 row created.

SQL> INSERT INTO testDML(c1, c3) VALUES (2, sysdate);

1 row created.

SQL> commit;

Commit complete.

SQL>

    ###########
    In the sdtandby database
    ###########
    SQL> delete from testDML where c1 = 1;

    1 row deleted.

    SQL>

SQL> update testDML set c1 = 3 where c1 = 2;

1 row updated.

SQL>
SQL> select  sql_text, user_name from v$open_cursor where user_name = 'SIMON' and upper(sql_text) like '%TESTDML%';

SQL_TEXT                                                                                             USER_NAME
---------------------------------------------------------------------------------------------------- --------------------
DELETE FROM "TESTDML" "A1" WHERE "A1"."C1"=1                                                         SIMON
update testDML set c1 = 3 where c1 = 2                                                               SIMON

SQL>

ORA-16397

If you receive the error ORA-16397: statement redirection from Oracle Active Data Guard standby database to primary database failed this is linked with the way that you connected in the database. If you use “/ as sysdba” without username and password, when you try to do the DML you hit the error. To solve this, connect using username, password and database:

[oracle@orcl19s ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 19:34:45 2019
Version 19.2.0.0.0

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


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);
INSERT INTO testDML(c1, c3) VALUES (1, sysdate)
            *
ERROR at line 1:
ORA-16397: statement redirection from Oracle Active Data Guard standby database
to primary database failed


SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0
[oracle@orcl19s ~]$ sqlplus sys/oracle@dg19s as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 14 19:34:55 2019
Version 19.2.0.0.0

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


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);

1 row created.

SQL> commit;

Commit complete.

SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Beta
Version 19.2.0.0.0
[oracle@orcl19s ~]$

ORA-16000

If you disable the ADG_REDIRECT_DML you will receive the error ORA-16000: database or pluggable database open for read-only access when you try to execute the DML:

SQL> alter system set ADG_REDIRECT_DML=false scope = both sid = '*';

System altered.

SQL> INSERT INTO testDML(c1, c3) VALUES (1, sysdate);
INSERT INTO testDML(c1, c3) VALUES (1, sysdate)
            *
ERROR at line 1:
ORA-16000: database or pluggable database open for read-only access


SQL>

If you have something to add, please coment!

Share it

Leave a Reply

Your email address will not be published. Required fields are marked *