Sunday, December 7, 2025

Oracle Transaction Guard Usage and Tips

How to handle duplicate transactions after a database failover in your Oracle RAC environment or Distributed env?

The moment of truth; did the transaction commit or not? is a classic high-availability headache.

Introduction to Oracle Transaction Guard:

This powerful feature is the key to achieving at-most-once execution for your critical business logic, even when a connection drops mid-commit.

Oracle Transaction Guard is a feature designed to provide a reliable way to determine the outcome of a distributed transaction in cases where the client-server communication is interrupted, such as network failures or system crashes. It helps ensure transaction consistency by enabling applications to safely retry transactions without risking duplicate work or data corruption.

 

In high-availability Oracle environments—especially RAC, Data Guard, application servers, and distributed systems—application failures during database outages can lead to transaction uncertainty. This happens when a session is interrupted after an application issues a COMMIT, but before it receives the commit confirmation. 

 

In such cases the application cannot know whether the transaction was committed, rolled back, or left incomplete. This can lead to data inconsistency, duplicate transactions, or user-facing errors.

 

Oracle Transaction Guard (TG) guarantees that an application can always determine the outcome of the last committed transaction—even across node failures, session kills, network timeouts, or planned maintenance.

 

Typical Transaction Guard Usage

The following pseudocode shows a typical usage of Transaction Guard:

Receive a FAN down event (or recoverable error)
 
FAN cancels the terminated session
 
If recoverable error  (new OCI_ATTRIBUTE for OCI, isRecoverable for JDBC)
     Get last LTXID from terminated session using getLTXID or from your callback
     Obtain a new session 
     Call GET_LTXID_OUTCOME with last LTXID to obtain COMMITTED and USER_CALL_COMPLETED status
 
If COMMITTED and USER_CALL_COMPLETED  
     Then return result
 
ELSEIF COMMITTED and NOT USER_CALL_COMPLETED   
     Then return result with a warning (that details such as out binds or row count were not returned)
 
ELSEIF NOT COMMITTED 
     Cleanup and resubmit request, or return uncommitted result to the client

 

Without Transaction Guard, an application that loses its database connection during a COMMIT must re-execute the transaction or attempt manual compensation. Both approaches risk:

 

Ø duplicate financial payments

Ø double bookings or orders

Ø lost or phantom commits

Ø data corruption in multi-tier apps

 

With Transaction Guard, Oracle ensures idempotent and safe retry logic. Applications can confidently resume processing without ambiguity.

 

Note: Avoiding duplication in the payments systems can handle by another techniques or combination of them such as in schema or SQL features, but in RAC env using TG is recommended.

 

 

How Transaction Guard Works

 

Transaction Guard uses a unique Global Transaction ID (GTRID) associated with each transaction. When a transaction completes, this GTRID and its outcome (committed or rolled back) are recorded persistently. If the client loses connection before receiving the commit confirmation, it can query the database with the GTRID to verify the exact outcome.

 

Oracle supports Transaction Guard through the Oracle Call Interface (OCI), JDBC, and other client APIs that are capable of sending transaction outcome inquiries. The feature is transparent to the database administrator but must be supported and utilized by the application or middleware.

The Problem TG Solves:

In a 3-node Oracle RAC setup, if a node fails right after your application sends a `COMMIT`, your client receives an error. Without TG, you don’t know if the commit succeeded on the server before the connection broke. Retrying the transaction risks a duplicate entry, leading to logical data corruption.

How it Works: The Logical Transaction ID (LTXID)

 Transaction Guard solves this uncertainty using the Logical Transaction ID (LTXID).

 Before a transaction, the JDBC driver retrieves a unique LTXID from the database.

If the connection fails during the commit, the application saves the last LTXID.

Upon reconnection (which might be to a different RAC node), the application uses the LTXID to call the server-side procedure DBMS_APP_CONT.GET_LTXID_OUTCOME`.

The database returns the definitive outcome: COMMITTED or NOT COMMITTED. No more guesswork!

 

This is essential for any application leveraging the high-availability of a multi-node RAC cluster.

 JDBC Example for RAC:

 I’ve put a small sample, working Java/JDBC example that demonstrates the connection setup for a RAC (using a service configured with `COMMIT_OUTCOME=TRUE`) and the crucial LTXID outcome check logic.

 Oracle Transaction Guard Setup for RAC

 Enabling Oracle Transaction Guard (TG) requires proper configuration of a database service using the `srvctl` utility in your Oracle RAC environment.

 

1. Creating the Service with `srvctl`

 To enable Transaction Guard, you must create a service and set the `COMMIT_OUTCOME` property to `TRUE`. This service should be used by all application clients that need to leverage TG.

 Here is the complete `srvctl` command to create a service named `TG_SERVICE` for a database named `RACDB` across your three RAC nodes (`racdb1`, `racdb2`, `racdb3`):

 

# 1. Add the service with COMMIT_OUTCOME=TRUE

 

--Consider load balancing based on your env. below settings is a sample only.

 

srvctl add service -db RACDB -service TG_SERVICE  -preferred racdb1,racdb2,racdb3  -commit_outcome TRUE  -failovertype TRANSACTION -failovermethod BASIC -failoverretry 30   -failoverdelay 10

--or

srvctl add service -database codedb -service GOLD -preferred serv1 -available serv2 -commit_outcome TRUE  -retention 604800

--or

DECLARE
  params dbms_service.svc_parameter_array;
BEGIN
  params('COMMIT_OUTCOME'):='true';
  params('RETENTION_TIMEOUT'):=604800;
  params('aq_ha_notifications'):='true';
  dbms_service.modify_service('<service-name>',params);
END;
/

 2. Start the service

 srvctl start service -db RACDB -service TG_SERVICE

 3. Verify the service configuration

 srvctl config service -db RACDB -service 


######JAVA Sample Code########

import java.sql.CallableStatement;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import oracle.jdbc.OracleConnection;

import oracle.jdbc.LogicalTransactionId;

import oracle.jdbc.pool.OracleDataSource;

 

/**

 * Complete JDBC example demonstrating Oracle Transaction Guard logic.

 * This example simulates a connection failure during a commit and

 * uses the Logical Transaction ID (LTXID) to check the transaction outcome

 * upon reconnection, ensuring at-most-once execution.

 *

 * NOTE: This code is conceptual. To run it, you must:

 * 1. Replace placeholders (USER, PASSWORD, HOSTs, SERVICE_NAME) with actual values.

 * 2. Ensure the database service (e.g., 'tg_service') is configured with COMMIT_OUTCOME=TRUE.

 * 3. Have the Oracle JDBC driver (ojdbcX.jar) in your classpath.

 * 4. Have a table named 'TEST_TABLE' with a column 'ID' (NUMBER) and 'DATA' (VARCHAR2).

 */

public class TransactionGuardExample {

 

    // --- 1. RAC Connection Details (Placeholders) ---

    private static final String USER = "your_user";

    private static final String PASSWORD = "your_password";

    private static final String SERVICE_NAME = "TG_SERVICE";

 

   // Must be configured with COMMIT_OUTCOME=TRUE

    private static final String SCAN_IP = "your_scan_ip"; // Placeholder for your Oracle SCAN Listener IP

 

    // Simplified RAC connection string using SCAN Listener

    private static final String URL = "jdbc:oracle:thin:@" + SCAN_IP + ":1521/" + SERVICE_NAME;

 

    // --- 2. Transaction Guard Outcome Constants ---

    // These correspond to the output of DBMS_APP_CONT.GET_LTXID_OUTCOME

    private static final int COMMITTED = 1;

    private static final int USER_CALL_COMPLETED = 2;

 

    public static void main(String[] args) {

        LogicalTransactionId lastLtxid = null;

        Connection conn = null;

 

        try {

            // --- Initial Connection Setup ---

            OracleDataSource ods = new OracleDataSource();

            ods.setURL(URL);

            ods.setUser(USER);

            ods.setPassword(PASSWORD);

            conn = ods.getConnection();

            conn.setAutoCommit(false);

            System.out.println("Successfully connected to the database.");

 

            // --- Transaction Attempt ---

            // 1. Get the LTXID *before* the transaction starts

            lastLtxid = ((OracleConnection) conn).getLogicalTransactionId();

            System.out.println("LTXID before transaction: " + lastLtxid);

 

            // 2. Execute the DML operation

            Statement stmt = conn.createStatement();

            stmt.executeUpdate("INSERT INTO TEST_TABLE (ID, DATA) VALUES (101, 'Transaction Guard Test')");

            System.out.println("DML executed successfully.");

 

            // 3. Attempt to commit

            System.out.println("Attempting to commit...");

            conn.commit();

            System.out.println("Commit successful (in a real scenario, this is where the failure would occur).");

 

            // --- SIMULATED FAILURE POINT ---

            // In a real-world scenario, the network or a RAC node fails here.

            // The client sends the commit but receives no confirmation.

            // We simulate this by forcing a close and catching the subsequent error.

            // For this example, we'll just proceed to the check logic as if a failure occurred.

 

        } catch (SQLException e) {

            // This catch block would typically handle the connection failure

            System.err.println("Connection failed during commit: " + e.getMessage());

            // The lastLtxid is preserved for the outcome check

        } finally {

            // Close the potentially broken connection

            if (conn != null) {

                try { conn.close(); } catch (SQLException e) { /* ignore */ }

            }

        }

 

        // --- 4. Outcome Check Logic (The core of Transaction Guard) ---

        if (lastLtxid != null) {

            System.out.println("\n--- Reconnecting to check transaction outcome ---");

            Connection newConn = null;

            try {

                // Re-establish connection (may connect to a different RAC node)

                OracleDataSource ods = new OracleDataSource();

                ods.setURL(URL);

                ods.setUser(USER);

                ods.setPassword(PASSWORD);

                newConn = ods.getConnection();

                // Call the server-side procedure to check the outcome

                String sql = "{call DBMS_APP_CONT.GET_LTXID_OUTCOME(?, ?, ?)}";

                CallableStatement cstmt = newConn.prepareCall(sql);

                // Input: The LTXID of the uncertain transaction

                cstmt.setObject(1, lastLtxid);

                // Output: The outcome (COMMITTED or USER_CALL_COMPLETED)

                cstmt.registerOutParameter(2, java.sql.Types.INTEGER);

                // Output: The status of the user call (optional, but good practice)

                cstmt.registerOutParameter(3, java.sql.Types.INTEGER);

                cstmt.execute();

                int outcome = cstmt.getInt(2);

                int callStatus = cstmt.getInt(3);

 

                if (outcome == COMMITTED) {

                    System.out.println("Outcome Check: Transaction with LTXID " + lastLtxid + " was COMMITTED.");

                } else if (outcome == USER_CALL_COMPLETED) {

                    System.out.println("Outcome Check: Transaction with LTXID " + lastLtxid + " was NOT COMMITTED (or rolled back). User call completed.");

                } else {

                    System.out.println("Outcome Check: Outcome is UNKNOWN or an error occurred.");

                }

                // In a real application:

                // - If COMMITTED, do nothing (transaction is done).

                // - If NOT COMMITTED, retry the transaction with the new connection.

            } catch (SQLException e) {

                System.err.println("Error during outcome check: " + e.getMessage());

            } finally {

                if (newConn != null) {

                    try { newConn.close(); } catch (SQLException e) { /* ignore */ }

                }

            }

        }

    }

}

Key Points for RAC + Transaction Guard:

 Ø Use Oracle JDBC Thin driver 12c or later.

 Ø Enable Fast Connection Failover (FCF) and Transaction Guard in the connection properties.

 Ø Connect using SCAN (Single Client Access Name) or a RAC-aware connect string with multiple nodes.

 Ø Use oracle.jdbc.OracleConnection.getTransactionOutcome() method after failure to verify the final transaction status.

 Ø Transaction Guard Coverage with Oracle Database 12c & Newer

 Ø You may use Transaction Guard on each database in your system including restarting on and failing over between

 Ø single instance database, Real Application Clusters, Data Guard and Active Data Guard.

 ♨️Transaction Guard is supported on Enterprise Edition and higher with the following Oracle Database configurations:

    » Single Instance Oracle RDBMS

  » Real Application Clusters

 » RAC One

   » Data Guard

    » Active Data Guard

  » Multitenant including unplug/plug and online PDB relocate

 » Global Data Services for the above database configurations

 Transaction Guard supports the following transaction types against Oracle Database 12c & newer:

    » Local transactions

    » DDL and DCL transactions

    » Distributed and Remote transactions

    » Parallel transactions

    » Commit on Success (auto-commit)

    » PL/SQL with embedded COMMIT

    » Starting with Oracle Database 12c Release 2 (12.2.0.1), XA transactions using One Phase Optimizations including XA commit flag TMONEPHASE and read optimizations

    » ALTER SESSION SET Container with Service clause, where the service uses Transaction Guard

 Transaction Guard supports the following v12c & newer client drivers:

 » JDBC-Thin Driver

 » OCI and OCCI client drivers

 » ODP.NET, Managed Driver

 » ODP.NET, Unmanaged Driver

 Transaction Guard Exclusions

 » Transaction Guard intentionally excludes recursive transactions and autonomous transactions so that these can be re-executed.

 Transaction Guard for Oracle 12c Release 2 excludes:

 » Active Data Guard with read/write DB Links for forwarding transactions

 » JDBC OCI is not supported, use JDBC thin driver

 » Two Phase XA transactions managed externally. When using XA, Transaction Guard returns the commit outcome for one phase XA transactions, and silently disables for externally-managed two-phase as this outcome is owned by the TP monitor (see Transaction Guard with XA Transactions)

 » Transaction Guard cannot be used in the TAF Callback for TAF or Application Continuity for OCI and ODP.NET, or in the JDBC initialization callback for Application Continuity for Java.

 TAF and Application Continuity (AC) are handling Transaction Guard internally.

 » Full database import cannot be executed with Transaction Guard enabled. Use an admin service without Transaction Guard for full database imports. User and object imports are not excluded.


Transaction Guard excludes failover across databases maintained by replication technology:

 Ø  Replication to Golden Gate

 Ø Replication to Logical Standby

 Ø Third Party replication solutions

 Ø  PDB clones clause (excluding PDB online relocation 12c Release 2 and newer)

If you are using a database replica using any replication technology such as Golden Gate or Logical Standby or 3rd party replication, you cannot use Transaction Guard between the primary and the secondary databases in this configuration. You may use Transaction Guard within each database that participates in the replication.

 In this use case, each database must use a different database identifier (DBID). Use V$DATABASE to obtain the DBID for each database.

 Transaction Guard with XA Transactions

Oracle Transaction Guard: XA Commit Outcome Made Reliable

Starting with Oracle Database 12c Release 2, Transaction Guard became smarter about handling XA transactions, especially in environments using TP Monitors and distributed transaction coordinators.

Here’s the key behavior:

  Supported transaction types

Ø  Local transactions using autocommit

Ø  Local transactions using explicit commits

Ø  XA transactions committing with TMONEPHASE
>>> Transaction Guard provides a guaranteed commit outcome.

 

 How Oracle handles XA Two-Phase Commit

When an XA transaction uses TMTWOPHASE:

Ø  Transaction Guard automatically disables itself for that transaction

Ø  A warning is returned if the API is called

Ø  The TP monitor is responsible for commit outcome

Ø  Transaction Guard is automatically re-enabled for the next transaction

 Why this matters:

This design allows TP Monitors to return a definitive and unambiguous commit outcome for TMONEPHASE operations, while respecting XA protocol rules for distributed two-phase commits.

If you're building high-availability, distributed, or RAC-based applications using XA, Transaction Guard gives you a reliable way to avoid duplicate commits and ensures your application always knows exactly what happened.

 

Summary: 

Oracle Transaction Guard provides a robust mechanism to achieve transaction outcome certainty in unreliable network conditions. By leveraging unique transaction identifiers and persistent outcome tracking, it allows applications to confidently handle failures and retries, ensuring data integrity and consistent application behavior.

 ***************************************

                Alireza Kamrani

No comments:

Post a Comment

Oracle Transaction Guard Usage and Tips

How to handle duplicate transactions after a database failover in your Oracle RAC environment or Distributed env? The moment of truth; did...