Oracle Tips and Tricks — David Fitzjarrell

April 27, 2022

“Put That Right Here…”

Filed under: General — dfitzjarrell @ 08:42

Sometimes interesting problems come out of processes that appear to be basic, boring tasks. Database migrations are a good example, especially if the migration requires a re-working of datetime calculations between one RDBMS and another. What may be taken for granted in the source RDBMS may produce incorrect results in the destination RDBMS when datetime arithmetic and limitations for some data types are not fully understood. Let’s look into that and, using a real-life example, see what can go wrong.

Oracle, for decades, has made datetime arithmetic basic and, well, simple; using SYSDATE it’s incredibly easy to create dates in the future as well as in the past:

SQL> alter session set nls_date_Format = 'DD-MON-RRRR HH24:MI:SS';
Session altered.
SQL> select sysdate, sysdate+10, sysdate-7 from dual;

SYSDATE              SYSDATE+10           SYSDATE-7
-------------------- -------------------- --------------------
26-APR-2022 09:47:01 06-MAY-2022 09:47:01 19-APR-2022 09:47:01

SQL>

In one SQL statement we have reported the current datetime, a datetime in the future and one in the past, all with simple addition and subtraction and notice that the time portion of the datetime value is also supplied. Other RDBMS engines are not as forgiving; Postgres requires that the datetime be converted to an interval prior to finding the difference between two datetime values, so that the date and the time elements are properly reported. A simple datetime difference in Oracle is coded:

select sysdate – expiry_date from expiry;

which returns the difference, including the fractional part of a day, which is fairly easily converted to days, hours and minutes with some basic mathematics:

SQL> select trunc(expiry_dt - sysdate) days,
  2         trunc(((expiry_dt - sysdate) - trunc(expiry_dt - sysdate))*24) hours,
  3         trunc(((expiry_dt - sysdate) - trunc(expiry_dt - sysdate))*1440)-
  4         trunc(((expiry_dt - sysdate) - trunc(expiry_dt - sysdate))*24)*60 minutes
  5  from expiry
  6  where
  7*        trunc(expiry_dt - sysdate) < 100
SQL> /

      DAYS      HOURS    MINUTES
---------- ---------- ----------
        26         23         47
        53         23         47
        80         23         47

SQL>

(I said basic, not short.) The only mathematical “gyrations” needed involve getting the fractional part of the day isolated and then getting that converted to hours and minutes with basic multiplication. No conversion of any date values to, say, intervals, was necessary to return the days and fraction thereof.

Postgres can do date subtraction, however if the date value is not converted to an interval with the proper time units it produces integer differences, meaning the time portion of the difference is “lost”. To address that the desired interval can be added to the datetime provided by now() or current_timestamp (minutes, seconds, hours) to return a more granular result. Executing a pg_sleep() within the plpgsql block doesn’t affect calls like current_timestamp and now() as the value from the initial call is preserved in the block context. As an example two functions are created, one that modifies a datetime value by adding an interval and one that simply relies upon the delay from a pg_sleep() call to provide a more recent time value; the code is provided below:

drop function test;
drop function test2;
set client_min_messages = notice;

CREATE FUNCTION test (nonsense integer)
RETURNS interval AS $$
DECLARE
   fst_date timestamp;
   scd_date timestamp;
BEGIN
    fst_date := now();
    raise notice 'First date: %',fst_date;
    scd_date := now() + interval '60 second';
    raise notice 'Second date: %',scd_date;
    RETURN scd_date - fst_date;
END;
$$ LANGUAGE plpgsql;


CREATE FUNCTION test2 (nonsense integer)
RETURNS interval AS $$
DECLARE
   fst_date timestamp;
   scd_date timestamp;
BEGIN
    fst_date := now();
    raise notice 'First date: %',fst_date;
    perform pg_sleep(5);
    scd_date := now();
    raise notice 'Second date: %',scd_date;
    RETURN scd_date - fst_date;
END;
$$ LANGUAGE plpgsql;

select test(1);
select test2(1);

The results from executing these functions are shown below — notice that subsequent calls to now() after a pg_sleep() execution (for 5 seconds) provide the same datetime value:

psql:/home/postgres/files/test.sql:38: NOTICE:  First date: 2022-04-27 07:12:39.635446
psql:/home/postgres/files/test.sql:38: NOTICE:  Second date: 2022-04-27 07:13:39.635446
   test
----------
 00:01:00
(1 row)

psql:/home/postgres/files/test.sql:39: NOTICE:  First date: 2022-04-27 07:12:39.636306
psql:/home/postgres/files/test.sql:39: NOTICE:  Second date: 2022-04-27 07:12:39.636306
  test2
----------
 00:00:00
(1 row)

[Longer values supplied to pg_sleep() provide the same results.]

A “raise notice” call, plus setting client_min_messages to notice, provides the NOTICE lines in the above output. Both functions call now() to set the datetime value; test also adds an interval of 60 seconds to the value returned by now() and does not call pg_sleep() to delay the second now() assignment. Function test2 uses only pg_sleep() in an attempt to get Postgres to return a more current value for now() — the results tell the tale. This can be confusing to those used to Oracle and its ability to use a dbms_lock.sleep() call to delay time and allow the RDBMS to return the current datetime when SYSDATE is called. When converting Oracle functions and procedures to PLPGSQL this can result in erroneous results as calculating datetime differences won’t ‘behave’ as Oracle developers expect. This is especially true when datetime differences in minutes or seconds are expected because the datetime calls won’t provide current results since the PLPGSQL context doesn’t appear to function that way. In certain cases an interval value can be used in a multiplication scenario to essentially convert the current datetime value to an interval type, providing hours, minutes and seconds when a datetime difference is calculated. Datetime values stored in table columns, when fetched, can undergo this transformation. Thorough testing is required to ensure that datetime differences within a Postgres installation provide the desired and expected results.

Code migrations between RDBMS engines can be daunting for a novice or moderately skilled developer as assumptions based on current behavior may not hold true in the destination RDBMS. Robust and rigorous testing should be done to “shake out” the code changes to ensure that correct functionality and results are provided. Failing to provide proper testing scenarios can cause issues “down the road” when migrated code doesn’t perform exactly as it did in the source implementation. Migrations take patience; when done properly that patience will be rewarded.

Put that code over here…

April 21, 2022

Whatcha Doin’???

Filed under: General — dfitzjarrell @ 09:32

Standby databases have become fairly commonplace in recent years. giving the DBA even more to monitor and manage during the normal business day. Five views are specific to the standby configuration:

V_$STANDBY_EVENT_HISTOGRAM
V_$STANDBY_LOG
GV_$STANDBY_LOG
GV_$MANAGED_STANDBY
V_$MANAGED_STANDBY

The GV_$ views are created for standby databases in a RAC configuration but can be used to monitor all standby databases. Let’s look at these views, what they report and how the information can be used to manage and triage a standby database.

V_$STANDBY_EVENT_HISTOGRAM reports on the apply lag for the standby (currently this is the only configured event for this view). It provides a history of the lag times and the dates when they were recorded. Sample output is shown below:

SQL> select * From v$standby_event_histogram;

NAME                                                                           TIME UNIT                          COUNT LAST_TIME_UPDATED
---------------------------------------------------------------- ------------------ ---------------- ------------------ --------------------
apply lag                                                                         0 seconds                     2029622 04/21/2022 10:24:36
apply lag                                                                         1 seconds                      126569 04/21/2022 10:24:29
apply lag                                                                         2 seconds                       74288 04/21/2022 10:21:33
apply lag                                                                         3 seconds                      113061 04/21/2022 10:15:17
apply lag                                                                         4 seconds                       94382 04/21/2022 10:15:18
apply lag                                                                         5 seconds                       82232 04/21/2022 09:43:01
apply lag                                                                         6 seconds                       74034 04/21/2022 09:33:13
apply lag                                                                         7 seconds                       68011 04/21/2022 06:57:59
apply lag                                                                         8 seconds                       63748 04/21/2022 06:58:00
apply lag                                                                         9 seconds                       60386 04/21/2022 06:58:01
apply lag                                                                        10 seconds                       57632 04/21/2022 06:58:02
apply lag                                                                        11 seconds                       55451 04/21/2022 06:58:03
apply lag                                                                        12 seconds                       53546 04/21/2022 06:35:55
apply lag                                                                        13 seconds                       51840 04/21/2022 06:35:56
apply lag                                                                        14 seconds                       50377 04/21/2022 06:35:57
apply lag                                                                        15 seconds                       49168 04/21/2022 06:35:58
apply lag                                                                        16 seconds                       47965 04/21/2022 06:35:59
apply lag                                                                        17 seconds                       47093 04/21/2022 06:36:00
apply lag                                                                        18 seconds                       46096 04/21/2022 06:36:01
apply lag                                                                        19 seconds                       45119 04/21/2022 06:34:08
apply lag                                                                        20 seconds                       44140 04/21/2022 06:29:02
apply lag                                                                        21 seconds                       43160 04/21/2022 06:29:03
apply lag                                                                        22 seconds                       42343 04/21/2022 06:29:04
apply lag                                                                        23 seconds                       41651 04/21/2022 06:29:05
apply lag                                                                        24 seconds                       41023 04/21/2022 06:29:06
apply lag                                                                        25 seconds                       40473 04/21/2022 06:29:07
apply lag                                                                        26 seconds                       39835 04/21/2022 06:29:08

NAME                                                                           TIME UNIT                          COUNT LAST_TIME_UPDATED
---------------------------------------------------------------- ------------------ ---------------- ------------------ --------------------
apply lag                                                                        27 seconds                       39214 04/21/2022 06:29:09
apply lag                                                                        28 seconds                       38762 04/21/2022 06:29:10
apply lag                                                                        29 seconds                       38298 04/21/2022 06:29:11
apply lag                                                                        30 seconds                       37779 04/21/2022 06:29:12
apply lag                                                                        31 seconds                       37332 04/21/2022 06:29:13
apply lag                                                                        32 seconds                       36856 04/21/2022 06:29:14
apply lag                                                                        33 seconds                       36459 04/21/2022 06:26:00
apply lag                                                                        34 seconds                       36030 04/21/2022 06:26:01
apply lag                                                                        35 seconds                       35446 04/21/2022 06:26:02
apply lag                                                                        36 seconds                       34974 04/21/2022 06:26:03
apply lag                                                                        37 seconds                       34433 04/21/2022 06:26:04
apply lag                                                                        38 seconds                       33739 04/21/2022 06:26:05
apply lag                                                                        39 seconds                       33063 04/21/2022 06:26:06
apply lag                                                                        40 seconds                       32409 04/21/2022 06:25:22
apply lag                                                                        41 seconds                       31876 04/21/2022 06:25:23
apply lag                                                                        42 seconds                       31362 04/21/2022 06:25:24
apply lag                                                                        43 seconds                       30789 04/21/2022 06:25:25
apply lag                                                                        44 seconds                       30172 04/21/2022 06:25:26
apply lag                                                                        45 seconds                       29661 04/21/2022 06:25:27
apply lag                                                                        46 seconds                       29120 04/21/2022 06:21:55
apply lag                                                                        47 seconds                       28504 04/21/2022 06:21:56
apply lag                                                                        48 seconds                       27965 04/21/2022 06:21:57
apply lag                                                                        49 seconds                       27307 04/21/2022 06:21:58
apply lag                                                                        50 seconds                       26734 04/21/2022 06:21:59
apply lag                                                                        51 seconds                       26183 04/21/2022 06:22:00
apply lag                                                                        52 seconds                       25681 04/21/2022 06:22:01
apply lag                                                                        53 seconds                       25235 04/21/2022 06:22:02

NAME                                                                           TIME UNIT                          COUNT LAST_TIME_UPDATED
---------------------------------------------------------------- ------------------ ---------------- ------------------ --------------------
apply lag                                                                        54 seconds                       24746 04/21/2022 06:22:03
apply lag                                                                        55 seconds                       24250 04/21/2022 06:22:04
apply lag                                                                        56 seconds                       23672 04/21/2022 06:22:05
apply lag                                                                        57 seconds                       23003 04/21/2022 06:22:06
apply lag                                                                        58 seconds                       21705 04/21/2022 06:22:07
apply lag                                                                        59 seconds                       19321 04/21/2022 06:15:38
apply lag                                                                         1 minutes                           0
apply lag                                                                         2 minutes                       17021 04/21/2022 06:15:39
apply lag                                                                         3 minutes                          19 04/05/2022 09:48:45

63 rows selected.

SQL>
                 

(Notice that records with a count of 0 have no date/time stamp because this is not an issue.) Using this view can provide starting points for lag investigations, hopefully so the issue can be diagnosed and resolved. Regular queries of this view should be executed to keep abreast of the apply lag during the day. Note that processes such as AWS Data Migration Service continuous replication can cause the standby to experience lags; such processes can be monitored through the V$SESSION_LONGOPS view.

V_$STANDBY_LOG (and GV_$STANDBY_LOG) report on the status and performance of the standby redo logs; sample output is shown below:

SQL> select * From v$standby_log
2 /

        GROUP# DBID                                                THREAD#          SEQUENCE#              BYTES          BLOCKSIZE               USED ARC STATUS          FIRST_CHANGE# FIRST_TIME

  NEXT_CHANGE# NEXT_TIME                           LAST_CHANGE# LAST_TIME

            10 3605096953                                                1             211077         1048576000                512           16694784 YES ACTIVE            91181333104 21-APR-2022 10:37:13
                                                    91181336755 21-APR-2022 10:37:28

            11 UNASSIGNED                                                1                  0         1048576000                512                  0 NO  UNASSIGNED


            12 UNASSIGNED                                                1                  0         1048576000                512                  0 NO  UNASSIGNED


            13 UNASSIGNED                                                1                  0         1048576000                512                  0 NO  UNASSIGNED


            14 UNASSIGNED                                                1                  0         1048576000                512                  0 YES UNASSIGNED

SQL >

For a single-instance standby the DBID need not be reported. As each standby log is accessed the report provides relevant information on the sequence number, status, first change number in the log and a progression of last change number as the standby logs are written to by the LGWR process. The GV_$ analog also reports the instance id, useful in a RAC configuration. From this view even more information can be returned by creating a bit more ‘useful’ script:

select group#,
thread#,
sequence#,
used,
round((used/bytes)100, 2) “%FULL”, archived, status, first_change#, first_time, last_change#, last_time, (last_time – first_time)86400 seconds_active,
round(used/((last_time – first_time)*86400),0) bytes_per_sec,
last_change#-first_change# changes
from v$standby_log;

The ouput of this query is provided below:


      GROUP#      THREAD#    SEQUENCE#         USED   %FULL ARC STATUS     FIRST_CHANGE# FIRST_TIME                    LAST_CHANGE# LAST_TIME                     SECONDS_ACTIVE BYTES_PER_SEC      CHANGES
------------ ------------ ------------ ------------ ------- --- ---------- ------------- ----------------------------- ------------ ----------------------------- -------------- ------------- ------------
          10            1       211077     59317760    5.66 YES ACTIVE       91181333104 21-APR-2022 10:37:13           91181347780 21-APR-2022 10:37:51                      38       1560994        14676
          11            1            0            0    0.00 NO  UNASSIGNED
          12            1            0            0    0.00 NO  UNASSIGNED
          13            1            0            0    0.00 NO  UNASSIGNED
          14            1            0            0    0.00 YES UNASSIGNED

Calculated values provide transfer rate per second, the active seconds for the log and the total number of recorded changes. This is another query that should be run occasionally every work day to monitor standby log activity.

V_$MANAGED_STANDBY gives the following output:

SQL> select * From v$managed_standby;

PROCESS            PID STATUS       CLIENT_P CLIENT_PID                               CLIENT_DBID                              GROUP#                                    RESETLOG_ID      THREAD#    SEQUENCE#       BLOCK#
--------- ------------ ------------ -------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ------------ ------------ ------------ ------------
      BLOCKS   DELAY_MINS KNOWN_AGENTS ACTIVE_AGENTS
------------ ------------ ------------ -------------
ARCH              8032 CLOSING      ARCH     8032                                     3605096953                               10                                         1003098742            1       211075      1726464
        1496            0            0             0

ARCH              8632 CONNECTED    ARCH     8632                                     3605096953                               N/A                                                 0            0            0            0
           0            0            0             0

ARCH              8634 CLOSING      ARCH     8634                                     3605096953                               10                                         1003098742            1       211077      1730560
        1063            0            0             0

ARCH              8638 CLOSING      ARCH     8638                                     3605096953                               11                                         1003098742            1       211076      1726464
         676            0            0             0

MRP0             10498 APPLYING_LOG N/A      N/A                                      N/A                                      N/A                                        1003098742            1       211078       597932
     2048000            0           17            17

RFS              12996 IDLE         ARCH     8832                                     3605096953                               N/A                                                 0            0            0            0
           0            0            0             0

RFS              30990 IDLE         UNKNOWN  8834                                     3605096953                               N/A                                                 0            0            0            0
           0            0            0             0

RFS              17662 IDLE         UNKNOWN  8825                                     3605096953                               N/A                                                 0            0            0            0
           0            0            0             0

RFS              12512 RECEIVING    LGWR     8881                                     3605096953                               1                                          1003098742            1       211078       597931

PROCESS            PID STATUS       CLIENT_P CLIENT_PID                               CLIENT_DBID                              GROUP#                                    RESETLOG_ID      THREAD#    SEQUENCE#       BLOCK#
--------- ------------ ------------ -------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ------------ ------------ ------------ ------------
      BLOCKS   DELAY_MINS KNOWN_AGENTS ACTIVE_AGENTS
------------ ------------ ------------ -------------
           2            0            0             0


9 rows selected.

SQL>

This can be used to monitor apply progress for the standby; the following query reports a more concise output that is easier to read:

select *
from v$managed_standby
where thread# > 0;

Formatting the output to remove dead space and report on only active threads produces:

SQL> @standby_apply

PROCESS            PID STATUS       CLIENT_P CLIENT_PI CLIENT_DBID  GROUP#     RESETLOG_ID      THREAD#    SEQUENCE#       BLOCK#       BLOCKS   DELAY_MINS KNOWN_AGENTS ACTIVE_AGENTS
--------- ------------ ------------ -------- --------- ------------ --------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ -------------
ARCH              8032 CLOSING      ARCH     8032      3605096953   10          1003098742            1       211075      1726464         1496            0            0             0
ARCH              8634 CLOSING      ARCH     8634      3605096953   10          1003098742            1       211077      1730560         1063            0            0             0
ARCH              8638 CLOSING      ARCH     8638      3605096953   11          1003098742            1       211076      1726464          676            0            0             0
MRP0             10498 APPLYING_LOG N/A      N/A       N/A          N/A         1003098742            1       211078      1130676      2048000            0           17            17
RFS              12512 RECEIVING    LGWR     8881      3605096953   1           1003098742            1       211078      1129050         1627            0            0             0

SQL >

Repeated executions will report apply progress for the standby, allowing the DBA to better see how the apply is running.

Yet another way to monitor standby database operations is the V$SESSION_LONGOPS view. The following query will produce a concise, yet usable, report on the standby activity:

set linesize 230 numwidth 15 pagesize 30 numwidth 18
column message format a95
column pct_done format 990.90
column sorter noprint
column min_remain format 999,990.90

select message, lower(message) sorter, sofar, case when totalwork=0 then sofar else totalwork end totalwork, (sofar/case when totalwork = 0 then sofar else totalwork end)*100 pct_done, round(time_remaining/60, 2) min_remain
from v$session_longops
where sofar <> totalwork
order by sorter;

The sorter column converts all of the message text to lower case so that entries are ordered alphabetically. Sample output is shown below:

SSQL> @longops_progress

MESSAGE                                                                                                      SOFAR          TOTALWORK PCT_DONE  MIN_REMAIN
----------------------------------------------------------------------------------------------- ------------------ ------------------ -------- -----------
Media Recovery: Active Apply Rate : 5724 out of 0 KB/sec done                                                 5724               5724   100.00
Media Recovery: Active Time : 984371 out of 0 Seconds done                                                  984371             984371   100.00
Media Recovery: Apply Time per Log : 104 out of 0 Seconds done                                                 104                104   100.00
Media Recovery: Average Apply Rate : 1171 out of 0 KB/sec done                                                1171               1171   100.00
Media Recovery: Checkpoint Time per Log : 1 out of 0 Seconds done                                                1                  1   100.00
Media Recovery: Elapsed Time : 4625569 out of 0 Seconds done                                               4625569            4625569   100.00
Media Recovery: Log Files : 9274 out of 0 Files done                                                          9274               9274   100.00
Media Recovery: Maximum Apply Rate : 42092 out of 0 KB/sec done                                              42092              42092   100.00
Media Recovery: Redo Applied : 5290078 out of 0 Megabytes done                                             5290078            5290078   100.00
select from v$logmnr_contents: Redo Blocks : 386465453992154 out of 0 Redo Blocks done             386465453992154    386465453992154   100.00
select from v$logmnr_contents: Redo Blocks : 389278657571034 out of 0 Redo Blocks done             389278657571034    389278657571034   100.00
select from v$logmnr_contents: Redo Blocks : 390884975339738 out of 0 Redo Blocks done             390884975339738    390884975339738   100.00
select from v$logmnr_contents: Redo Blocks : 391722493962458 out of 0 Redo Blocks done             391722493962458    391722493962458   100.00
select from v$logmnr_contents: Redo Blocks : 391924357425370 out of 0 Redo Blocks done             391924357425370    391924357425370   100.00
select from v$logmnr_contents: Redo Blocks : 391941537294554 out of 0 Redo Blocks done             391941537294554    391941537294554   100.00
select from v$logmnr_contents: Redo Blocks : 445031628040410 out of 0 Redo Blocks done             445031628040410    445031628040410   100.00
select from v$logmnr_contents: Redo Blocks : 445031628040410 out of 0 Redo Blocks done             445031628040410    445031628040410   100.00
select from v$logmnr_contents: Redo Blocks : 445031628040410 out of 0 Redo Blocks done             445031628040410    445031628040410   100.00

18 rows selected.

SQL >

The standby in this example is currently being used for AWS DMS replication, which explains the appearance of the v$logmnr_contents queries. The normal standby processes are categorized as Media Recovery, on the top of the query output, and reports the apply rate, time the standby is active, apply time per log, elapsed time, the number of log files processed, the maxiimum apply rte and the megabytes of redo processed. Should an apply lag be present it will also be shown in the Media Recovery output. V$SESSION_LONGOPS reports on a combination of areas covered by the other views, but in a single report which may be easier to generate and digest.

All of the queries provided can be put into a single script and configured for scheduled execution, either through cron (on *NIX systems), the Windows task manager or any other available scheduling product to produce regular reports that can be emailed to the DBA team. Sucb reports are extremely useful when managing a standby configuration as important areas of concern can be reported and, should issues arise, be addressed in a timely manner. Simply decide upon a “reasonable” schedule based upon the usage the standby is subject to so that reports will be both accurate and timely. Remember, the faster the DBA can recognize and identify an issue the faster it can be addressed and, hopefully, resolved. That’s good for everybody.

So, whatcha doin’?

April 20, 2022

ORA-01555, again?!?!?!?

Filed under: General — dfitzjarrell @ 15:42

Modern times call for modern methods, and that’s especially true with Oracle and AWS Data Migration Services (DMS). Where once the venerable standby stood silent and alone, quietly maintaining synchronization with the primary. it can now be used as a replication source for projects like populating and maintaining data, gleaned from Oracle, into Postgres, MySQL or other RDBMS engines. This, of course, gives the DBA more to monitor and, on occasion, more to troubleshoot. Let’s look at a situation where the standby is throwing ORA-01555 errors, discover the cause and take measures to address the issue.

The ORA-01555 error hsa been discused before, in quite a bit of detail, with respect to a live, fully functioning read-write database. But now we must consider the standby as a data source and investigate the ORA-01555 using a different perspective. First, lst’s set up the situation where these errors occur. Using AWS Data Migration Services tools to replicate data into a Postgres database from Oracle a couple of processes are usually executed to either populate destination tables or maintain the migrated data. One uses LogMiner, one uses SQL queries, and it’s the process using SQL queries that can throw the dreaded ORA-01555.

The general reason the ORA-01555 rears its ugly head is time — queries that rely on the read consistency mechanism Oracle employs will be affected if the query is exceedingly long running or if the data pull takes a considerable amount of time. It could be that, in reconstructing the data image, enough transactions are processed to overwrite data referenced by the starting SCN (the SCN at which the query begins accessing data). Once that occurs Oracle can’t ensure the data in the data blocks that once had that SCN still provide usable data and, boom, the ORA-01555 stops the query dead in its tracks. Yet another enemy of the read-consistency mechanism can appear: it simply takes too long to insert data into the destination table and the SCN is overwritten by time.

Setting up the ‘proper’ conditions:

A Postgres database is configured with destination tables, complete with indexes, designed to contain large amounts of migrated data. For the sake of argument the following table will be the target of the data migration:

CUSTOMERS(
CUST_ID numeric,
CUST_NAME varchar(60),
CUST_ADDR1 varchar(80),
CUST_ADDR2 varchar(80),
CUST_CITY varchar(80),
CUST_STATE varchar(2),
CUST_COUNTRY varchar(80),
CUST_ZIP varchar(10),
CUST_PHONE varchar(12),
CUST_EMAIL varchar(100),
CUST_DISC numeric)

The following indexes are also configured:

CUST_ID_IDX CUSTOMERS.CUST_ID UNIQUE
CUST_NAME_IDX CUSTOMERS.CUST_NAME
CUST_LOC_IDX CUSTOMERS.CUST_CITY,CUSTOMERS.CUST_STATE,CUSTOMERS.CUST_COUNTRY

Data will be pulled from a table named CUST_DATA, in the Oracle standby database, containing data for 100,000 customers world-wide, many with multiple locations, providing 550,000 rows. [The actual migration involved considerably more data; for the sake of illustration this much smaller data set will be used.]

The DMS load process begins, pulling data from the standby and inserting it into Postgres. At the start of the process the volume of data is growing but is not overly large, so inserts, along with keeping the indexes updated, flow rather quickly. As the table grows in size the inserts take longer to complete, partly due to the presence of the various indexes. At the ‘tipping point’ the table is so large, the index maintenance operations consume longer and longer times, and the driving query encounters the situation where the before image is no longer considered valid by Oracle, and the error is thrown. Most DBAs would be searching the Internet for possible solutions, finding the common suggestion to increase the undo_retention to just longer than the longest query duration reported when the ORA-01555 is thrown. Dutifully following the instructions provided the undo_retention is increased and the data migration is started again. Unfortunately the error is again thrown, at about the same elapsed time in the migration run. Monitoring the replication progress using the V$SESSION_LONGOPS view the DBA notices that as the migration progresses pauses in the select progress get longer and longer. Regardless of what the DBA changes on the Oracle side the error again appears. Frustrated the DBA starts looking elsewhere for causes, and notices the steadily increasing time intervals between activating the data feed and inserting the data into Postgres. And the light goes on.

Ensuring that the index creations are scripted so they can be run at a later time the DBA suggests that the indexes on the destination table be dropped for the initial data load. This is done and the data migration is again started; monitoring the initial load processes, again using the V$SESSION_LONGOPS view, now shows that the select statement processing the CUSTOMERS table is proceeding at a faster pace. So far, so good, thinks the DBA, fully prepared to see yet another ORA-01555 disrupt the data flow. But, with the indexes gone from the destination table, the insert processing maintains a quicker pace and completes without interruption. Once the table is fully loaded the indexes can be created, which does take some time but won’t fail with an ORA-01555.

In this situation the ORA-01555 is thrown due to longer and longer processing times for the initial data load using DMS, caused by the overhead of index maintenance during the insert phase of the data migration. Dropping the indexes reduced the overall processing time considrably, allowing Postgres to more efficiently process the inserts. Other, more ‘traditional’ causes can also be present during data migration operations, and these were the first items addressed by the intrepid DBA. It was when those actions had little to no apparent effect on the success of the migration that the DBA had to consider other areas that could be affecting the migration. Success was achieved because the DBA left no stone unturned when analyzing the problem.

Since the V$SESSION_LONGOPS view may not be familiar to some a query and configuration parameters is provided below:

set linesize 230 numwidth 15 pagesize 30 numwidth 18
column message format a95
column pct_done format 990.90
column sorter noprint
column min_remain format 999,990.90

select message, lower(message) sorter, sofar, case when totalwork=0 then sofar else totalwork end totalwork, (sofar/case when totalwork = 0 then sofar else totalwork end)*100 pct_done, round(time_remaining/60, 2) min_remain
from v$session_longops
where sofar <> totalwork
order by sorter;

When saved as a script it will allow the DBA to monitor progress of the select and LogMiner processes during DMS operations. The MIN_REMAIN column is the estimated minutes remaining until the operation completes. Since it is a dynamic estimate it can either remain the same or increase as the select progresses; this is not a malfunction. Patience will be rewarded during the monitoring process. Also, the message column is translated to all lower-case characters for the sorting operation; this column is not displayed in the output.

It can’t be stressed enough that simply because this migration was affected by indexes on the destination table other migrations will suffer the same fate. [The actual migration this post is based upon involved approximately 2,200,000,000 rows, not the 550,000 posted in the example. The ORA-01555 appeared consistently eight hours into the data migration. And indexes on the destination table were, indeed, the culprit.] But it is worth taking the time to consider every possible cause when such errors arise, as the most obvious possibility may not be the most likely possibility.

Modern times.

April 13, 2022

Ready … Set … GO!!!

Filed under: General — dfitzjarrell @ 07:57

Interesting questions can be posted in the Oracle database forums and one post generated a great deal of attention. A query and part of the execution plan were posted with the poster asking how to get rid of the table scan. The table has a primary key index so, on the face of it, it is interesting that the primary key wasn’t used. Of course to provide any sort of insight or solution the table and index definitions need to be provided. In a response from the original post the CREATE TABLE and CREATE INDEX statements were provided. Let’s take a look at these objects and see how this question was addressed. I am indebted to both Jonathan Lewis and Andrew Sayer for providing the various query re-writes offered here.

Looking at the definition for the ‘problem’ table (LF_HOTEL_TEMP) we find only two columns, both of which are in the primary key definition. The tables were created:

SQL> create table LF_HOTEL_TEMP
2 (
3 HOTEL_CODE VARCHAR2(4000),
4 SERVICE_ID NUMBER(3)
5 )
6 /

Table created.

SQL>
SQL> create table LF_TS_ROOMTYPE_PROPERTIES
2 (
3 HOTEL_CODE VARCHAR2(20),
4 ROOM_TYPE VARCHAR2(500),
5 BOARD_TYPE VARCHAR2(500),
6 ROOM_AMT FLOAT,
7 SERVICE_ID NUMBER,
8 CURRENCY_CODE VARCHAR2(10)
9 )
10 /

Table created.

SQL>
SQL> create table LF_HB_ROOMTYPE_PROPERTIES
2 (
3 HOTEL_CODE VARCHAR2(20),
4 ROOM_TYPE VARCHAR2(500),
5 BOARD_TYPE VARCHAR2(500),
6 ROOM_AMT FLOAT,
7 SERVICE_ID NUMBER,
8 CURRENCY_CODE VARCHAR2(10)
9 )
10 /

Table created.

SQL>
SQL> create table LF_GTA_ROOMTYPE_PROPERTIES
2 (
3 HOTEL_CODE VARCHAR2(20),
4 ROOM_TYPE VARCHAR2(500),
5 BOARD_TYPE VARCHAR2(500),
6 ROOM_AMT FLOAT,
7 SERVICE_ID NUMBER,
8 CURRENCY_CODE VARCHAR2(10)
9 )
10 /

Table created.

SQL>

and then the primary key and additional indexes were created:

SQL> alter table lf_hotel_temp
2 add constraint lf_hotel_temp_PK primary key (HOTEL_CODE,service_id)
3 /

Table altered.

SQL>
SQL> create index LF_hb_roomtype_prop_IDX on lf_hb_roomtype_properties (HOTEL_CODE)
2 /

Index created.

SQL>
SQL> create index LF_ts_roomtype_prop_IDX on lf_ts_roomtype_properties (HOTEL_CODE)
2 /

Index created.

SQL>
SQL> create index LF_gta_roomtype_prop_IDX on lf_gta_roomtype_properties (HOTEL_CODE)
2 /

Index created.

SQL>

The original poster reported 278,000 rows in the LF_HOTEL_TEMP table so data was generated to reproduce that nunber of rows. Next the remaining tables were populated so that none of the three remaining tables shared any data:

SQL> begin
2 for i in 1..278000 loop
3 insert into lf_hotel_temp
4 values(i, mod(i,999)+1);
5 end loop;
6
7 commit;
8 end;
9 /

PL/SQL procedure successfully completed.

SQL>
SQL> insert into lf_ts_roomtype_properties
2 (hotel_code)
3 select hotel_code from lf_hotel_temp
4 where service_id < 511;

142058 rows created.

SQL> insert into lf_ts_roomtype_properties
2 (hotel_code)
3 select hotel_code from lf_hotel_temp
4 where service_id between 313 and 642;

91740 rows created.

SQL> insert into lf_gta_roomtype_properties
2 (hotel_code)
3 select hotel_code from lf_hotel_temp
4 where service_id between 271 and 823;

153743 rows created.

SQL> insert into lf_hb_roomtype_properties
2 (hotel_code)
3 select hotel_code from lf_hotel_temp
4 where service_id between 571 and 999;

119262 rows created.

SQL> COMMIT;

Commit complete.

SQL>

Let’s look at the original query and its execution plan:

SQL> set autotrace on linesize 140
SQL>
SQL> — original query
SQL> SELECT a.hotel_code
2 FROM lf_hotel_temp a
3 WHERE a.service_id = : p_service_id
4 AND (NOT EXISTS (SELECT *
5 FROM lf_ts_roomtype_properties b
6 WHERE a.hotel_code = b.hotel_code)
7 or NOT EXISTS (SELECT *
8 FROM lf_gta_roomtype_properties b
9 WHERE a.hotel_code = b.hotel_code)
10 or NOT EXISTS (SELECT *
11 FROM lf_hb_roomtype_properties b
12 WHERE a.hotel_code = b.hotel_code));

HOTEL_CODE

1998
999
5994

243756
235764
238761

278 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 4111332730

-----------------------------------------------------------------------------------------------
| Id  | Operation          | Name                     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                          |     3 |    33 |   512   (4)| 00:00:01 |
|*  1 |  FILTER            |                          |       |       |            |          |
|*  2 |   TABLE ACCESS FULL| LF_HOTEL_TEMP            |   278 |  3058 |    94  (16)| 00:00:01 |
|*  3 |   INDEX RANGE SCAN | LF_TS_ROOMTYPE_PROP_IDX  |     1 |     7 |     3   (0)| 00:00:01 |
|*  4 |   INDEX RANGE SCAN | LF_GTA_ROOMTYPE_PROP_IDX |     1 |     7 |     1   (0)| 00:00:01 |
|*  5 |   INDEX RANGE SCAN | LF_HB_ROOMTYPE_PROP_IDX  |     1 |     7 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( NOT EXISTS (SELECT 0 FROM "LF_TS_ROOMTYPE_PROPERTIES" "B" WHERE
              "B"."HOTEL_CODE"=:B1) OR  NOT EXISTS (SELECT 0 FROM "LF_GTA_ROOMTYPE_PROPERTIES" "B"
              WHERE "B"."HOTEL_CODE"=:B2) OR  NOT EXISTS (SELECT 0 FROM "LF_HB_ROOMTYPE_PROPERTIES"
              "B" WHERE "B"."HOTEL_CODE"=:B3))
   2 - filter("A"."SERVICE_ID"=TO_NUMBER(:P_SERVICE_ID))
   3 - access("B"."HOTEL_CODE"=:B1)
   4 - access("B"."HOTEL_CODE"=:B1)
   5 - access("B"."HOTEL_CODE"=:B1)



Statistics
----------------------------------------------------------
         11  recursive calls
          0  db block gets
       2077  consistent gets
        872  physical reads
          0  redo size
       4119  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         20  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        278  rows processed






SQL>

All but LF_HOTEL_TEMP use the indexes to speed access to the data; this may be due to the fact that LF_HOTEL_TEMP is a two-column table and the priomary key index will be larger than the table since it contains the table data and a rowid for each row. The main idea illustrated by this problem involves being able to find a logically equivalent way of writing the SQL. Jonathan Lewis decided that set operations might produce a ‘better’ plan and re-wrote the query as shown below, producing a plan accessing LF_HOTEL_TEMP by the primary key index:

SQL> — JL solution
SQL> var p_service_id number
SQL> exec : p_service_id := 1

PL/SQL procedure successfully completed.

SQL>
SQL> select /*+ dynamic_sampling(0) */
2 hotel_code
3 from lf_hotel_temp
4 where service_id = :p_service_id
5 minus (
6 select hotel_code
7 from lf_ts_roomtype_properties
8 where hotel_code is not null
9 intersect
10 select hotel_code
11 from lf_gta_roomtype_properties
12 where hotel_code is not null
13 intersect
14 select hotel_code
15 from lf_hb_roomtype_properties
16 where hotel_code is not null
17 )
18 ;

HOTEL_CODE

100899
101898
102897

999
9990
99900

278 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 775735246

--------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                     | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                          |     1 |  2159 |     4 (100)| 00:00:01 |
|   1 |  MINUS                |                          |       |       |            |          |
|   2 |   SORT UNIQUE NOSORT  |                          |     1 |  2015 |     1 (100)| 00:00:01 |
|*  3 |    INDEX FULL SCAN    | LF_HOTEL_TEMP_PK         |     1 |  2015 |     0   (0)| 00:00:01 |
|   4 |   INTERSECTION        |                          |       |       |            |          |
|   5 |    INTERSECTION       |                          |       |       |            |          |
|   6 |     SORT UNIQUE NOSORT|                          |     4 |    48 |     1 (100)| 00:00:01 |
|*  7 |      INDEX FULL SCAN  | LF_TS_ROOMTYPE_PROP_IDX  |     4 |    48 |     0   (0)| 00:00:01 |
|   8 |     SORT UNIQUE NOSORT|                          |     4 |    48 |     1 (100)| 00:00:01 |
|*  9 |      INDEX FULL SCAN  | LF_GTA_ROOMTYPE_PROP_IDX |     4 |    48 |     0   (0)| 00:00:01 |
|  10 |    SORT UNIQUE NOSORT |                          |     4 |    48 |     1 (100)| 00:00:01 |
|* 11 |     INDEX FULL SCAN   | LF_HB_ROOMTYPE_PROP_IDX  |     4 |    48 |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("SERVICE_ID"=TO_NUMBER(:P_SERVICE_ID))
       filter("SERVICE_ID"=TO_NUMBER(:P_SERVICE_ID))
   7 - filter("HOTEL_CODE" IS NOT NULL)
   9 - filter("HOTEL_CODE" IS NOT NULL)
  11 - filter("HOTEL_CODE" IS NOT NULL)



Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       5996  consistent gets
       2416  physical reads
     234680  redo size
       4119  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         20  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        278  rows processed

                                                                                                             

SQL>

Andrew Sayer then provided another pair of re-writes, shown below with the plans returned by Oracle 12.1.0.2:

SQL> — Andrew Sayer
SQL>
SQL> with existance_check as (select hotel_code
2 from (select distinct hotel_code
3 from lf_ts_roomtype_properties
4 where hotel_code is not null
5 union all
6 select distinct hotel_code
7 from lf_gta_roomtype_properties
8 where hotel_code is not null
9 union all
10 select distinct hotel_code
11 from lf_hb_roomtype_properties
12 where hotel_code is not null
13 )
14 group by hotel_code
15 having count(*) = 3
16 )
17 SELECT a.hotel_code
18 FROM lf_hotel_temp a
19 WHERE a.service_id = : p_service_id
20 AND NOT EXISTS (SELECT *
21 FROM existance_check b
22 WHERE a.hotel_code = b.hotel_code)
23 /

HOTEL_CODE

252747
131868
118881

73926
197802
192807

278 rows selected.

Execution Plan

Plan hash value: 1887592732


| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |

Execution Plan
----------------------------------------------------------
Plan hash value: 1887592732

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name                       | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |                            |  2899 |  5738K|       |  3454   (9)| 00:00:01 |
|*  1 |  HASH JOIN ANTI          |                            |  2899 |  5738K|  5744K|  3454   (9)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL      | LF_HOTEL_TEMP              |  2899 |  5704K|       |    98  (20)| 00:00:01 |
|   3 |   VIEW                   |                            |   611K|  7162K|       |  2843   (9)| 00:00:01 |
|*  4 |    FILTER                |                            |       |       |       |            |          |
|   5 |     HASH GROUP BY        |                            |   611K|  7162K|       |  2843   (9)| 00:00:01 |
|   6 |      VIEW                |                            |   611K|  7162K|       |  2736   (5)| 00:00:01 |
|   7 |       UNION-ALL          |                            |       |       |       |            |          |
|   8 |        HASH UNIQUE       |                            |   312K|  3660K|  6136K|  1387   (5)| 00:00:01 |
|*  9 |         TABLE ACCESS FULL| LF_TS_ROOMTYPE_PROPERTIES  |   312K|  3660K|       |    74  (15)| 00:00:01 |
|  10 |        HASH UNIQUE       |                            |   176K|  2063K|  3464K|   796   (5)| 00:00:01 |
|* 11 |         TABLE ACCESS FULL| LF_GTA_ROOMTYPE_PROPERTIES |   176K|  2063K|       |    55  (13)| 00:00:01 |
|  12 |        HASH UNIQUE       |                            |   122K|  1438K|  2416K|   553   (5)| 00:00:01 |
|* 13 |         TABLE ACCESS FULL| LF_HB_ROOMTYPE_PROPERTIES  |   122K|  1438K|       |    37  (14)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("A"."HOTEL_CODE"="B"."HOTEL_CODE")
   2 - filter("A"."SERVICE_ID"=TO_NUMBER(:P_SERVICE_ID))
   4 - filter(COUNT(*)=3)
   9 - filter("HOTEL_CODE" IS NOT NULL)
  11 - filter("HOTEL_CODE" IS NOT NULL)
  13 - filter("HOTEL_CODE" IS NOT NULL)

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)



Statistics
----------------------------------------------------------
         37  recursive calls
          0  db block gets
       2664  consistent gets
       1056  physical reads
      37440  redo size
       4119  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         20  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        278  rows processed

                                                                                                                   

SQL>
SQL> with existance_check as (select hotel_code
2 from lf_ts_roomtype_properties
3 where hotel_code is not null
4 intersect
5 select hotel_code
6 from lf_gta_roomtype_properties
7 where hotel_code is not null
8 intersect
9 select hotel_code
10 from lf_hb_roomtype_properties
11 where hotel_code is not null
12 )
13 SELECT a.hotel_code
14 FROM lf_hotel_temp a
15 WHERE a.service_id = : p_service_id
16 AND NOT EXISTS (SELECT *
17 FROM existance_check b
18 WHERE a.hotel_code = b.hotel_code)
19 /

HOTEL_CODE

252747
131868
118881

73926
197802
192807

278 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1347650198

-------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                       | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                            |  2899 |  5738K|       |  3048   (6)| 00:00:01 |
|*  1 |  HASH JOIN RIGHT ANTI  |                            |  2899 |  5738K|  2880K|  3048   (6)| 00:00:01 |
|   2 |   VIEW                 |                            |   122K|  1438K|       |  2736   (5)| 00:00:01 |
|   3 |    INTERSECTION        |                            |       |       |       |            |          |
|   4 |     INTERSECTION       |                            |       |       |       |            |          |
|   5 |      SORT UNIQUE       |                            |   312K|  3660K|  6136K|            |          |
|*  6 |       TABLE ACCESS FULL| LF_TS_ROOMTYPE_PROPERTIES  |   312K|  3660K|       |    74  (15)| 00:00:01 |
|   7 |      SORT UNIQUE       |                            |   176K|  2063K|  3464K|            |          |
|*  8 |       TABLE ACCESS FULL| LF_GTA_ROOMTYPE_PROPERTIES |   176K|  2063K|       |    55  (13)| 00:00:01 |
|   9 |     SORT UNIQUE        |                            |   122K|  1438K|  2416K|            |          |
|* 10 |      TABLE ACCESS FULL | LF_HB_ROOMTYPE_PROPERTIES  |   122K|  1438K|       |    37  (14)| 00:00:01 |
|* 11 |   TABLE ACCESS FULL    | LF_HOTEL_TEMP              |  2899 |  5704K|       |    98  (20)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("A"."HOTEL_CODE"="B"."HOTEL_CODE")
   6 - filter("HOTEL_CODE" IS NOT NULL)
   8 - filter("HOTEL_CODE" IS NOT NULL)
  10 - filter("HOTEL_CODE" IS NOT NULL)
  11 - filter("A"."SERVICE_ID"=TO_NUMBER(:P_SERVICE_ID))

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)



Statistics
----------------------------------------------------------
         14  recursive calls
          0  db block gets
       1848  consistent gets
          0  physical reads
          0  redo size
       4119  bytes sent via SQL*Net to client
        570  bytes received via SQL*Net from client
         20  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
        278  rows processed

                                                                                                                 

SQL>

Four approaches, four different plans, but all generating the same result set. In the reported statistics from Jonathan Lewis’ query the redo size is much larger than from the other plans, however the plan for Jonathan’s query is the only one using an index on LF_HOTEL_TEMP. It is possible that very small data sets will produce other execution plans, with more of them using the indexes, however using a data set sized the same as the one the original poster is using makes for more ‘honest’ comparisons. In general, the set operations appear to be more efficient than the non-set operations.

Using set operations in a relational database may not be the most obvious of choices, but it is good to remember that SQL is, at its heart, a set-based language. When thinking about alternative ways to solve a problem the best way may not be the one that is most commonly used; this problem brings that into focus. Many people don’t think about database data in terms of sets, but, really, that is what a SQL query generates, a result set. Thinking about data that way may open up more efficient ways to return those sets.

And you’re all set.

April 7, 2022

“Making Time Stand Still … Sorta …”

Filed under: General — dfitzjarrell @ 08:49

Sometimes it would be nice to make time stand still, using a date in the past, in the future, or simply to preserve the current date/time primarily for testing purposes. Wonder of wonders Oracle provides a way to set the database to consistently return the same date and time from SYSDATE regardless of when the request is made using the FIXED_DATE parameter. Let’s look at how to set that, unset it and discover what it affects.

The FIXED_DATE setting affects the SYSDATE call, but doesn’t affect any internal date/time requests the database will make. Redo logs are not affected, the alert log is not affected, nor is any mechanism that relies on the current date/time. When FIXED_DATE is set to a given date/time critical functions of the database will still use the system clock for timing and record keeping activities. For those not familiar with what requires the current date/time here are a few items in that list:

  • System Change Numbers (SCNs)
  • Redo
  • Undo
  • Read-consistency
  • Backups
  • Standby recovery
  • GoldenGate

These processes use UTC to record date/time information, which is unaffected by timezone variations and Daylight Savings Time. The alert log and other database records may report date/time using the currently defined timezone but underneath it all is UTC. [When the system clock switches from DST to “normal” time and the same dates/times are reported twice in the alert log (and other places) Oracle relies on the SCN, which is not dependent on local time, so that Oracle is not “confused” by the duplication.) Since those processes use a different date/time call setting FIXED_DATE doesn’t affect critical database functionality.

Setting FIXED_DATE requires nothing more than ALTER SYSTEM privilege and the database does not need to be shutdown and started to effect the change. It can use the current NLS_DATE_FORMAT setting, which is reported using the following command:

show parameter nls_date_format

This produces:

SQL > show parameter nls_date_format

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
nls_date_format                      string      DD-MON-YYYY HH24:MI:SS
SQL >

Regardless of the current setting it’s best to set FIXED_DATE using the standard date/time format reported in the Oracle documentation, which is RRRR-MM-DD HH24:MI:SS. To set FIXED_DATE to December 31st, 1999:

SQL> alter system set fixed_date = ‘1999-12-31 00:00:00’;

System altered.

SQL> select sysdate from dual;

SYSDATE

31-DEC-99

SQL>

The value is a string so it must be enclosed in single quotes as shown above. Regardless of the time of day, or even the date, as long as FIXED_DATE is set to a valid date it will consistently return that date/time; setting ‘time on’ in a sqlplus session illustrates this:

16:52:34 SQL> set time on
16:52:40 SQL> /

TO_CHAR(SYSDATE,’RR

1999-12-31 00:00:00

16:52:42 SQL> /

TO_CHAR(SYSDATE,’RR

1999-12-31 00:00:00

16:52:43 SQL> /

TO_CHAR(SYSDATE,’RR

1999-12-31 00:00:00

16:52:44 SQL> /

TO_CHAR(SYSDATE,’RR

1999-12-31 00:00:00

16:52:45 SQL>

As the system clock advances time the SYSDATE call steadfastly returns the configured fixed date. Until FIXED_DATE is either set to a different date/time or turned off entirely SYSDATE will be unwavering in its insistence on the FIXED_DATE value.

As mentioned above FIXED_DATE can be set using the current NLS_DATE_FORMAT:

22:49:23 SQL> alter session set nls_date_format = ‘RRRR-MON-DD HH24:MI:SS’;

Session altered.

22:49:51 SQL> alter system set fixed_date = ‘1999-DEC-31 00:00:00’;

Session altered.

22:49:51 SQL> select sysdate from dual;

SYSDATE

1999-DEC-31 00:00:00

22:50:06 SQL> /

SYSDATE

1999-DEC-31 00:00:00

22:50:08 SQL> /

SYSDATE

1999-DEC-31 00:00:00

22:50:09 SQL> /

SYSDATE

1999-DEC-31 00:00:00

22:50:10 SQL>

Turning off FIXED_DATE is as simple as setting the parameter to NONE:

16:57:01 SQL> alter system set fixed_date=none;

System altered.

16:57:16 SQL> select to_char(sysdate, ‘RRRR-MM-DD HH24:MI:SS’) from dual;

TO_CHAR(SYSDATE,’RR

2022-04-04 16:57:38

16:57:38 SQL> /

TO_CHAR(SYSDATE,’RR

2022-04-04 16:57:40

16:57:40 SQL> /

TO_CHAR(SYSDATE,’RR

2022-04-04 16:57:41

16:57:41 SQL>

FIXED_DATE is set in the init.ora or spfile.ora for the given database by the ‘alter system’ statement; because of this it does need to be turned off when it is no longer needed. Errors in setting this (for example when the date string doesn’t match the NLS_DATE_FORMAT and isn’t the fully qualified date string) can result in the following message being displayed:

17:05:41 SQL> alter system set fixed_date=’May 31, 1999′;
alter system set fixed_date=’May 31, 1999′
*
ERROR at line 1:
ORA-32017: failure in updating SPFILE
ORA-02078: invalid setting for ALTER SYSTEM FIXED_DATE

FIXED_DATE is not changed:

17:09:04 SQL> select to_char(sysdate, ‘RRRR-MM-DD HH24:MI:SS’) from dual;

TO_CHAR(SYSDATE,’RR

2022-04-04 17:10:28

17:10:28 SQL> /

TO_CHAR(SYSDATE,’RR

2022-04-04 17:10:30

17:10:30 SQL> /

TO_CHAR(SYSDATE,’RR

2022-04-04 17:10:30

17:10:30 SQL>

This prevents incorrect formats from causing error messages as FIXED_DATE will remain at its current setting. It’s best to use the format specified in the Oracle documentation to prevent an ORA-02078 error on startup since non-stasndard NLS date formats may not be set until after the database has started.

As this is designed for testing purposes its use in production environments is, well, frowned upon. Maintaining a constant SYSDATE in production will affect inserts and updates utilizing SYSDATE for column population; FIXED_DATE does not affect the SYSTIMESTAMP value which could create confusing results when inserts and updates utilize both SYSDATE and SYSTIMESTAMP values:

17:49:05 SQL> create table dt_test(
17:49:12 2 my_dt date,
17:49:19 3 my_ts timestamp with local time zone)
17:49:32 4 /

Table created.

17:49:33 SQL> insert into dt_test
17:49:40 2 values(sysdate, systimestamp);

1 row created.

17:49:52 SQL> select * From dt_test;

MY_DT

MY_TS

31-DEC-99
04-APR-22 05.49.52.611706 PM

17:49:59 SQL>

Both columns are supposed to contain the current calendar date/time, but as can be seen from this example when FIXED_DATE is set the values will not match. [Granted it isn’t likely that both a DATE and TIMESTAMP will be recorded when only one is required, but it can be an issue when a creation_date is recorded when a row is inserted and a timestamp is recorded when the row is updated, say when an order is entered and processed on the same day:

18:10:09 SQL> create table orders(
18:10:09 2 ord_no number not null,
18:10:09 3 order_dt date,
18:10:09 4 processed varchar2(1) default ‘N’,
18:10:09 5 proc_dt timestamp with local time zone,
18:10:09 6 ship_dt timestamp with local time zone,
18:10:09 7 carrier varchar2(45));

Table created.

18:10:09 SQL>
18:10:09 SQL> alter table orders add constraint orders_pk primary key(ord_no);

Table altered.

18:10:09 SQL>
18:10:09 SQL> create sequence order_seq start with 1 increment by 1 nocycle nomaxvalue;

Sequence created.

18:10:09 SQL>
18:10:09 SQL> create or replace trigger orderno_trg
18:10:09 2 before insert on orders
18:10:09 3 for each row
18:10:09 4 begin
18:10:09 5 :new.ord_no := order_seq.nextval;
18:10:09 6 end;
18:10:09 7 /

Trigger created.

18:10:09 SQL>
18:10:09 SQL> insert into orders(order_dt)
18:10:09 2 values(sysdate);

1 row created.

18:10:09 SQL>
18:10:09 SQL> commit;

Commit complete.

18:10:09 SQL>
18:10:09 SQL> exec dbms_lock.sleep(120);

PL/SQL procedure successfully completed.

18:12:09 SQL>
18:12:09 SQL> update orders
18:12:09 2 set proc_dt = systimestamp
18:12:09 3 where ord_no = 1;

1 row updated.

18:12:09 SQL>
18:12:09 SQL> commit;

Commit complete.

18:12:09 SQL>
18:12:09 SQL> exec dbms_lock.sleep(120);

PL/SQL procedure successfully completed.

18:14:09 SQL>
18:14:09 SQL> update orders
18:14:09 2 set ship_dt = systimestamp,
18:14:09 3 carrier = ‘Sherm”s Shipping’
18:14:09 4 where ord_no = 1;

1 row updated.

18:14:09 SQL>
18:14:09 SQL> commit;

Commit complete.

18:14:09 SQL>
18:14:09 SQL> select * From orders;

ORD_NO ORDER_DT  P

PROC_DT

SHIP_DT

CARRIER

 1 31-DEC-99 N

04-APR-22 06.12.09.301695 PM
04-APR-22 06.14.09.485234 PM
Sherm’s Shipping

18:14:09 SQL>

The database table reports the order was entered on December 31, 1999 yet it wasn’t procesed and shipped until almost 22 years later, which makes for some interesting record keeping. This was due to FIXED_DATE being set in the production environment; possibly this was a case where the DBA was in the wrong database when FIXED_DATE was set. This is easily rectified by setting FIXED_DATE to none, as mentioned previously. The table can also be updated:

update orders
set ord_dt = proc_dt
where ord_no = 1;

This would produce the following results:

ORD_NO ORDER_DT  P

PROC_DT

SHIP_DT

CARRIER

     1 04-APR-22 N

04-APR-22 06.12.09.301695 PM
04-APR-22 06.14.09.485234 PM
Sherm’s Shipping

Now the dates and timestamps coincide (although the time portion of the DATE value will not reflect the actual time the order was entered). Other manipulations can be performed to apprximate the actual order entry time, but those won’t be discussed here. Suffice it to say that the DBA (probably you) should verify the environment he/she is working in to prevent such mishaps from occurring.

[Having fixed_date set prior to a database shutdown MAY cause issues when the database is restarted. This will occur when fixed_date is set using a non-default date format; the default format is DD-MON-RR. Other formats, even though the documentation states that any valid date format is usable , are allowed only when the database is running — such implicit date conversions are not available until. Date conversions are not available until the database is open. The error that will be displayed is:

ORA-00065: initialization of FIXED_DATE failed

The fix is to create a file from. the spill, edit the file to either set fixed_daet to none or remove the parameter entirely. Start t he database with t hep file, create a new spill from the current file then shutdown and start t he database one more time.]

FIXED_DATE can be a boon to development and testing as a consistent date can be provided when needed. It can be a possible disaster if it’s set in the wrong environment (like production), so care is required when setting and using it. Use it wisely and carefully and there should be no issues.

Unless you’re actually transported to December 31, 1999…

April 5, 2022

“Let The Migration Begin!”

Filed under: General — dfitzjarrell @ 15:19

Oracle has long been found in data centers worldwide and many a DBA has “cut their teeth”, so to speak, administering on-premises databases and servers. Recently, however, databases, including Oracle, have migrated “to the cloud”, ushering in a new era in database management. This movement has, obviously, triggered data migration from those older, “earth-bound” databases to a cloud infrastructure of elastic storage and dynamically allocated CPU and RAM resources. This shift also requires a relatively new beast, Data Migration Services, to transfer data from the old world to the new. For Oracle this usually involves the use of DBMS_LOGMNR in some capacity. Let’s look at what that entails and what problems can arise during a data migration run.

Data migration can occur between two (or more) databases from the same vendor or two (or more) databases of different vendors. When a source is an Oracle database performance can be affected since the DBMS_LOGMNR utility can consume considerable server resources (CPU and RAM) at process startup. It all comes down to what the LogMiner process has to do to get the data changes required to update the destination database. Knowing where LogMiner gets the data and how it searches these resources will provide the keys to understanding the change in system load across CPU, RAM and disc.

The LogMiner process (initiated and managed by the DBMS_LOGMNR package) extracts data from the online redo logs and associated archive logs based upon several criteria. The “usual suspects” in that list are:

  • Table owner
  • Table name
  • System Change Number

Some basic configuration heeds to be completed prior to using LogMiner to successfully find and transfer data based on the principle of Change Data Capture, or CDC. The basic structure of the redo data is that of change vectors — records are stored in the redo logs that contain the rowid, columns changed and the respective data that was changed. That is sufficient to allow a database to operate and recover in the event of a failure. However more information is needed when using CDC to apply changes to another database, so Oracle provides the option of supplemental logging, where tables can be configured to record more of the changes into the redo stream. Primary key columns, foreign key columns, unique key columns, even an option to record all table columns are configurable to allow Oracle to record as much change as possible. After these adjustments are made LogMiner csn be summoned to do its bidding.

LogMiner relies on either:

  • Primary key columns

or

  • Unique key columns (which may be different than a declared primary key)

or

  • The Oracle rowid

to distinctly identify data. When moving data across platforms (Oracle to Postgres, for example) the Oracle ROWID won’t do much good, thus primary or unique keys must be defined at both the source and the destination. That being said most data migration scenarios involving Oracle configure all of the available options for supplemental logging in an effort to capture all such changes. This, of course, increases the data volume in the redo stream, making the task of searching that stream more resource intensive.

CPU cycles are impacted as well, as additional server-level processes need to start and run. This leads to additional memory consumption on the source Oracle database server, impacting every other running process. So starting the LogMiner process can seriously impact the underlying server hosting the source Oracle database. Oracle does provide a way to allocate memory resources to the LogMiner process; the STREAMS pool is the target area being adjusted during this activity. Since Oracle implements dynamic SGA/PGA or overall memory management shifting the available resources to meet demand is automatic. This shifting of resources can be monitored at a basic level using the V$SGA_DYNAMIC_COMPONENTS view, which reports on the major areas of memory configuration and allocation within the database. Sample data found in this view is shown below:

SQL > select * From v$sga_dynamic_components;

COMPONENT                                                        CURRENT_SIZE   MIN_SIZE   MAX_SIZE USER_SPECIFIED_SIZE OPER_COUNT LAST_OPER_TYP LAST_OPER LAST_OPER_TIME                GRANULE_SIZE
---------------------------------------------------------------- ------------ ---------- ---------- ------------------- ---------- ------------- --------- ----------------------------- ------------
shared pool                                                        1509949440 1241513984 1979711488                   0         18 GROW          IMMEDIATE 05-APR-2022 12:33:13              33554432
large pool                                                           67108864   67108864  771751936                   0        542 SHRINK        DEFERRED  05-APR-2022 13:41:30              33554432
java pool                                                            33554432   33554432   67108864                   0          1 SHRINK        DEFERRED  30-MAR-2022 18:39:29              33554432
streams pool                                                        100663296  100663296  100663296           100663296          0 STATIC                                                    33554432
DEFAULT buffer cache                                               1.0603E+10 9932111872 1.0872E+10                   0        561 GROW          DEFERRED  05-APR-2022 13:41:30              33554432
KEEP buffer cache                                                           0          0          0                   0          0 STATIC                                                    33554432
RECYCLE buffer cache                                                        0          0          0                   0          0 STATIC                                                    33554432
DEFAULT 2K buffer cache                                                     0          0          0                   0          0 STATIC                                                    33554432
DEFAULT 4K buffer cache                                                     0          0          0                   0          0 STATIC                                                    33554432
DEFAULT 8K buffer cache                                                     0          0          0                   0          0 STATIC                                                    33554432
DEFAULT 16K buffer cache                                                    0          0          0                   0          0 STATIC                                                    33554432
DEFAULT 32K buffer cache                                                    0          0          0                   0          0 STATIC                                                    33554432
Shared IO Pool                                                      503316480  503316480  503316480           503316480          0 STATIC                                                    33554432
ASM Buffer Cache                                                            0          0          0                   0          0 STATIC                                                    33554432

14 rows selected.

SQL >

Should the STREAMS pool be set to 0 (the default configuration setting) Oracle will “steal” memory from the DEFAULT buffer cache and shift it to the STREAMS pool. This can be a fairly intensive operation, which is why the STREAMS pool should be configured to a reasonable non-zero value to minimize memory reallocations “on-the-fly”. Remember that while such memory management is active new server processes, tied to new database processes, are also vying for server resources and the combined CPU/RAM hits may be experienced by the users connected to the database performing other work.

While all of this is happening Oracle, through the LogMiner utility, is starting to populate the V$LOGMNR_CONTENTS view so that whatever application is called by the database vendor or cloud service provider can search for data in the redo stream. (When it rains, it pours.) End-users valiantly attempting to perform their daily duties can often find sloggish response during the startup and initial search phases of LogMiner operation. Once initialized the resource demands reduce, but continuous data mining operations during DMS activities still add overhead to what would normally be a standard work day. Multiple LogMiner queries (sometimes 6 or more) can be executing simultaneously; these ongoing data feeds are far less intrusive than those at startup or those performing inital data loads into the migration destination but they still consume resources that would normally be available to the end users.

On-going DMS processes can be a regular occurrence if both the Oracle source database and the destination database are used simultaneously — for example an Oracle source is the main “home” for application tables and data while a MySQL database, configured with microservices, feed portions of the application data to the application and both participate in the overall data processing. To expand on that let’s say the MySQL database is the front-end for an order entry/order processing application and the Oracle side still contains the inventory, manufacturing, packing and shipping modules, sending order status data back to MySQL. DMS processes will need to run so that the order status data can be inserted into the various MySQL tables that provide data to the front-end application. In that design DMS processes are part of the processing path and will be running almost constantly to keep pace with the online orders. Given that implementation the database servers (both physical and virtual) should be configured with substantial CPU, RAM and disc resources in anticipation of peak periods of activity. This, of course, is where “the cloud” stands head and shoulders above most physical configurations, allowing for dynamic allocation of additional CPUs, RAM and storage when needed. Still, there is likely a physical server (or two or three or four) working hard to keep up with the cloud-generated pace, making performance a possible issue; realizing this in the initial design phase can make such implementations less susceptible to large processing loads. Planning is everything.

DBAs are rarely consulted when these types of designs are first given consideration, and are the first to receive blame when performance doesn’t meet expectations. Anything the DBA can do to monitor and proactively address such issues will go a long way in ensuring success with these endeavors, and knowing where in the processing chain the weaker links are gives the DBA a “leg up”, so to speak, on making the end users, and management, happier. This post should provide that to the affected DBA, preparing him or her to effectively address performance issues as they occur.

Miracles do happen.

April 4, 2022

Can I Speak To The Manager?

Filed under: General — dfitzjarrell @ 09:21

In days of old managing a standby configuration was a manual process, as was creating the standby and starting managed recovery. None of this is exceptionally difficult, but it could be a time-consuming job putting all of the pieces into place — the log_archive_dest_2 setting, starting the standby database and enabling the managed recovery process. Modern times provide modern tools to, hopefully, simplify some tasks and standby database configuration has been given the benefit of the Data Guard Broker, along with the dgmgrl utility, to help configure and manage these standby databases. Let’s look at dgmgrl and see what it provides.

For those not familiar with a standby database the basic setup contains the following elements:

  • Primary database
  • Standby database
  • TNS configuration

There are other items to configure but those are the basic parts to ensure connectivity and managed recovery are successful. The essential processes involved are ARCH and LGWR; since current standby databases rely on standby redo logs the ARCH process is essentialy a backup, transfering archivelogs to the standby as they get copied to the configured destination on the primary. Should the LGWR process have issues the archived redo logs will be used to continue recovery. When these databases were manually created and managed it was the DBA’s job to monitor the network transfer rates to ensure the standby would remain synchronized with the primary, necessitating various shell scripts to report on network throughput, etc. The overall health of the configuration was also reported using scripts to query the primary and standby and provide information on recovery and any lag encountered.

Enter dgmgrl, the Oracle utility designed to configure and help manage standby database configurations. It is a versastile tool that aids the DBA in creating, modifying and monitoring the standby configuraiton. But before dgmgrl can be used the dg_broker_start parameter msut be set to TRUE on both the primary and the standby database; if the broker is not running dgmgrl cannot perform its “magic”. Since the standby should already be running and recovering, network connectivity between the two should have been established, allowing dgmgrl to connect to either database in a given standby configuration. Once connectivity is established using dgmgrl to create and manage a standby configuration is relatively easy. The first step is to enable the broker on both databases:

alter system set dg_broker_start=true;

Once this is done we start dgmgrl and begin the configuration process:

$ dgmgrl /
DGMGRL for Linux: Version 11.2.0.4.0 – 64bit Production

Copyright (c) 2000, 2009, Oracle. All rights reserved.

Welcome to DGMGRL, type “help” for information.
Connected.
DGMGRL>

The next step is to create the configuration, and the following command will do just that; this will take a short amount of time as dgmgrl modifies configuration parameters in the primary:

DGMGRL> create configuration my_dguard_config as primary database is snard connect identifier is snard;
Configuration “my_dguard_config” created with primary database “snard”
DGMGRL>

Now that we have a configuration we can add the standby database:

DGMGRL> add database snard_stby as connect identifier is snard_stby maintained as physical;
Database “snard_stby” added
DGMGRL>

It’s time to enable the configuration, and this will also take some time to complete:

DGMGRL> enable configuration;
Enabled.
DGMGRL>

If all went as planned (it did as there were no errors or warnings displayed) we can check the configuration and verify success:

DGMGRL> show configuration;

Configuration – my_dguard_config

Protection Mode: MaxPerformance
Databases:
snard – Primary database
snard_stdby – Physical standby database

Fast-Start Failover: DISABLED

Configuration Status:
SUCCESS

DGMGRL>

The returned status should report SUCCESS. Now we can use dgmgrl to monitor the configuration, including transfer rates to the standby; the “show database” command using the standby database name provides the following information:

DGMGRL> show database snard_stdby;

Database – snard_stdby

Role: PHYSICAL STANDBY
Intended State: APPLY-ON
Transport Lag: 0 seconds (computed 0 seconds ago)
Apply Lag: 0 seconds (computed 0 seconds ago)
Apply Rate: 4.03 MByte/s
Real Time Query: ON
Instance(s):
snard

Database Status:
SUCCESS

DGMGRL>

Among the information provided is the transport lag, apply lag and the apply rate so the health of the remote connection can be determined. Knowing the health of the configuration is key to keeping the standby running and synchronized with the primary. As a secnodary check the standby can be queried to provide addtional information:

set linesize 300 trimspool on numwidth 15 pagesize 30
column name format a60
column message format a80
column client_pid format a10
column client_dbid format a12
column group# format a15

column thread# format 9999999

— When tmux is not active this can be commented

— set pause on

column “SYSDATE” new_value sys_dt noprint

select to_char(sysdate, ‘RRRRMMDD_HH24MI’) “SYSDATE” from dual;

spool /home/oracle/scripts/tuning/reports/standby/standby_&sys_dt..log

— Reports for primary database

— No rows returned for standby

prompt *
prompt * Logs sent to standby *
prompt *
SELECT THREAD#, SEQUENCE#, CREATOR, REGISTRAR, APPLIED FROM V$ARCHIVED_LOG
WHERE SEQUENCE# >= (SELECT MAX(SEQUENCE#) – 10 FROM V$ARCHIVED_LOG)
AND STANDBY_DEST = ‘YES’
ORDER BY SEQUENCE#;

prompt *
prompt * Protection mode *
prompt *
SELECT DATABASE_ROLE, DB_UNIQUE_NAME INSTANCE, OPEN_MODE,
PROTECTION_MODE, PROTECTION_LEVEL, SWITCHOVER_STATUS
FROM V$DATABASE
WHERE DATABASE_ROLE=’PRIMARY’;

prompt *
prompt * Fast-start failover *
prompt *
SELECT FS_FAILOVER_STATUS FSFO_STATUS, FS_FAILOVER_CURRENT_TARGET
TARGET_STANDBY, FS_FAILOVER_THRESHOLD THRESHOLD,
FS_FAILOVER_OBSERVER_PRESENT OBS_PRES
FROM V$DATABASE
WHERE DATABASE_ROLE=’PRIMARY’;

prompt *
prompt * Redo apply *
prompt *
SELECT *
FROM V$MANAGED_STANDBY
WHERE THREAD# <> 0;

column group# format 999999999999999
prompt *
prompt * Standby redo logs *
prompt *
select group#,
thread#,
sequence#,
used,
round((used/bytes)100, 2) “%FULL”, archived, status, first_change#, first_time, last_change#, last_time, (last_time – first_time)86400 seconds_active,
round(used/((last_time – first_time)*86400),0) bytes_per_sec,
last_change#-first_change# changes
from v$standby_log
where exists (select 1 from v$database where database_role = ‘PHYSICAL STANDBY’);

prompt *
prompt * Synchronization report *
prompt *
SELECT ARCHIVED_THREAD#, ARCHIVED_SEQ#, APPLIED_THREAD#, APPLIED_SEQ#
FROM V$ARCHIVE_DEST_STATUS
WHERE APPLIED_THREAD# > 0;

prompt *
prompt * Apply lag report *
prompt *
SELECT THREAD#, LOW_SEQUENCE#, HIGH_SEQUENCE#
FROM V$ARCHIVE_GAP
where exists (select 1 from v$database where database_role = ‘PHYSICAL STANDBY’);

prompt *
prompt * Registered logs report *
prompt *
SELECT REGISTRAR, CREATOR, THREAD#, SEQUENCE#, FIRST_CHANGE#,
NEXT_CHANGE# FROM V$ARCHIVED_LOG
WHERE SEQUENCE# >= (SELECT MAX(SEQUENCE#) – 10 FROM V$ARCHIVED_LOG)
AND THREAD# > 0;

prompt *
prompt * Log history *
prompt *
SELECT THREAD#, SEQUENCE#, FIRST_CHANGE#, NEXT_CHANGE#
FROM V$LOG_HISTORY
WHERE SEQUENCE# >= (SELECT MAX(SEQUENCE#) – 10 FROM V$ARCHIVED_LOG);

set pause off
clear columns
spool off

Such a report is excellent for displaying current statistics on the standby and its recovery. Two important pieces are the Synchronization report and the Apply lag report; the Synchronization report displays the following data:


  • Synchronization report *

ARCHIVED_THREAD# ARCHIVED_SEQ# APPLIED_THREAD# APPLIED_SEQ#


           1          208185               1          208185

Note that the archived sequence and the applied sequence match; this is, obviously, the ideal situation as the apply lag report will display:


  • Apply lag report *

no rows selected

[Should the applied sequence not match the archived sequence these reports change:


  • Synchronization report *

ARCHIVED_THREAD# ARCHIVED_SEQ# APPLIED_THREAD# APPLIED_SEQ#


           1          192731               1          192641

  • Apply lag report *

THREAD# LOW_SEQUENCE# HIGH_SEQUENCE#


   1          192642          192730

The missing sequence numbers are reported so that archivelog recovery can be executed. Once archivelogs are restored recovery closes the gap while still taking transactions through the standby redo logs. Unless the gap is composed of a larg number of logs (I am aware of a situation where over 1000 logs went “missing” because the standby was not monitored. In that situation it may be faster to simply rebuild the standby.) The dgmgrl utility would also report the lag and attempt to resolve it via the FAL server configuration. If an FAL server is not available the lag is not resolved until manual intervention is performed. Using the script may be an easier way to report the lag and resolve it.]

The dgmgrl utility can also allow the DBA to test a failover scenario without actually failing the database over; this is accomplished using the switchover command, which temporarily swaps roles between the databases. Executing a switchover verifies that the standby location can be used as the primary without the necessity of rebuilding the standby afterwards. Additionally a switchover works both ways, so a switchover to the standby and a subsequent switchover back to the primary can be executed. Such testing can provide the “warm fuzzies” that the standby can be used in short order should the need arise.

Using the verbose option to “show database” will display the normal short list plus all of the parameters and associated values for the standby configuration. It is a long list that won’t be provided here. The dgmgrl utility can also be used to set or modify any of the listed paramters; normally this won’t be necessary but occasionally what dgmgrl sets and what the database has set don’t match. Warning messages are genarated when broker parameters differ from currently set database parameters and dgmgrl can be used to modify “offending” parameters to match the database settings. The “edit” command is used to modify a configuration, database or instance; an example is shown below:

EDIT DATABASE database-name
SET PROPERTY property-name = value;

Use the value from the database parameter to match the database broker property to the database init parameter value. (This is sometimes easier than modifying the database parameter to match the broker configuration since the database won’t need to be restarted afterwards.) This will need to be done for each parameter providing a warning from dgmgrl; once completed a “show database” command should report SUCCESS indicating a “proper” configuration. A similar command can be used to edit a configuration parameter:

EDIT CONFIGURATION SET PROPERTY property-name = value;

The protection mode can also be edited:

EDIT CONFIGURATION SET PROTECTION MODE AS MAXAVAILABILITY;

This command can be used on any database in the configuration from any database in the configuration, allowing management of the entire configuration from one location (usually this is the primary).

Although this has been a rather brief introduction/instruction sheet for the dgmgrl utility the goal is to get you a solid start on using it to help manage your Data Guard installation. Oracle’s documentation provides a wealth of information on dgmgrl, and a link to that information is provided below:

Oracle’s dgmgrl command-line reference:
https://docs.oracle.com/cd/E11882_01/server.112/e40771/dgmgrl.htm#DGBKR585

That provides commands, examples and other information you might find useful. It covers the entire spectrum of dgmgrl functionality so it should be considered as a complete command reference.

Data Guard installations can sometimes be complex, involving multiple standby databases ih various forms of recovery. The dgmgrl utility allows monitoring and management of every database in a Data Guard configuration using the broker, making that task easier. It won’t make you an instant expert, but it is worth the time to learn its use and capabilities.

Unless you like antiques.

Blog at WordPress.com.