Grundlegendes zu XA-Transaktionen

JDBC-Treiber herunterladen

Microsoft JDBC-Treiber für SQL Server unterstützt optional verteilte Transaktionen für die Java-Plattform, Enterprise Edition/JDBC 2.0. JDBC-Verbindungen von der Klasse SQLServerXADataSource können Bestandteil normaler verteilter Transaktionsverarbeitungsumgebungen wie Anwendungsserver für die Java-Plattform, Enterprise Edition (Java EE) sein.

In diesem Artikel steht XA für erweiterte Architektur.

Warnung

Der Microsoft JDBC-Treiber 4.2 (und höher) für SQL enthält neue Timeoutoptionen für die vorhandene Funktion für den automatischen Rollback nicht vorbereiteter Transaktionen. Ausführliche Informationen finden Sie weiter unten in diesem Artikel unter Konfigurieren von serverseitigen Timeouteinstellungen für den automatischen Rollback nicht vorbereiteter Transaktionen.

Bemerkungen

Für die Implementierung verteilter Transaktionen stehen die folgenden Klassen zur Verfügung:

Klasse Implementiert BESCHREIBUNG
com.microsoft.sqlserver.jdbc.SQLServerXADataSource javax.sql.XADataSource Das Klassenfactory für verteilte Verbindungen.
com.microsoft.sqlserver.jdbc.SQLServerXAResource javax.transaction.xa.XAResource Der Ressourcenadapter für den Transaktions-Manager.

Hinweis

Verbindungen für verteilte XA-Transaktionen verwenden standardmäßig die Isolationsstufe "Lesen mit Commit".

Richtlinien und Einschränkungen für die Verwendung von XA-Transaktionen

Die folgenden zusätzlichen Richtlinien gelten für eng gekoppelte Transaktionen:

  • Wenn Sie XA-Transaktionen zusammen mit MS DTC (Microsoft Distributed Transaction Coordinator) verwenden, stellen Sie möglicherweise fest, dass die aktuelle Version von MS DTC eng verkoppeltes XA-Verzweigungsverhalten nicht unterstützt. MS DTC verfügt beispielsweise über eine 1:1-Zuordnung zwischen einer Transaktions-ID für eine XA-Verzweigung (XID) und einer MS DTC-Transaktions-ID. Die von lose verbundenen XA-Verzweigungen ausgeführten Aktionen sind voneinander isoliert.

  • MS DTC unterstützt auch eng verknüpfte XA-Branches, bei denen mehrere XA-Branches mit der gleichen globalen Transaktions-IDs (GTRID) einer einzelnen MS DTC-Transaktions-ID zugewiesen werden. Durch diese Unterstützung können mehrere eng verkoppelte XA-Verzweigungen die jeweiligen Änderungen im Ressourcen-Manager anzeigen, beispielsweise SQL Server.

  • Das Flag SSTRANSTIGHTLYCPLD ermöglicht, dass die Anwendungen die eng verkoppelten XA-Transaktionen verwenden können, die verschiedene XA-Verzweigungstransaktions-IDs (BQUAL) aufweisen, jedoch über dieselbe globale Transaktions-ID (GTRID) und Format-ID (FormatID) verfügen. Damit Sie dieses Feature verwenden können, müssen Sie SSTRANSTIGHTLYCPLD für den flags-Parameter der XAResource.start-Methode festlegen:

    xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
    

Konfigurationsanweisungen

Die folgenden Schritte sind erforderlich, wenn Sie XA-Datenquellen zusammen mit Microsoft Distributed Transaction Coordinator (MS DTC) verwenden möchten. Dazu werden die folgenden allgemeinen Schritte ausgeführt:

  1. Stellen Sie sicher, dass der MS DTC-Dienst ausgeführt und automatisch gestartet wird.
  2. Konfigurieren Sie serverseitige Komponenten.
  3. Konfigurieren Sie ein serverseitigen Timeout (optional).
  4. Gewähren Sie Benutzer*innen Zugriff.

Ausführen des MS DTC-Diensts

Der MS DTC-Dienst muss im Service Manager als Automatisch markiert werden, damit er beim Starten des SQL Server-Diensts ausgeführt wird. Führen Sie die folgenden Schritte aus, um MS DTC für XA-Transaktionen zu aktivieren:

Unter Windows Vista und höher:

  1. Klicken Sie auf Start, geben Sie im Feld Suche starten den Namen dcomcnfg ein, und drücken Sie dann die EINGABETASTE, um Komponentendienste zu öffnen. Sie können auch im Feld Suche starten den Pfad %windir%\system32\comexp.msc eingeben, um Komponentendienste zu öffnen.

  2. Erweitern Sie Komponentendienste, Computer, Arbeitsplatz und dann Distributed Transaction Coordinator.

  3. Klicken Sie mit der rechten Maustaste auf Lokaler DTC, und wählen Sie anschließend Eigenschaften aus.

  4. Klicken Sie im Dialogfeld Eigenschaften von Lokaler DTC auf die Registerkarte Sicherheit.

  5. Aktivieren Sie das Kontrollkästchen XA-Transaktionen ermöglichen, und klicken Sie anschließend auf OK. Daraufhin wird der MS DTC-Dienst neu gestartet.

  6. Klicken Sie erneut auf OK, um das Dialogfeld Eigenschaften zu schließen, und schließen Sie anschließend Komponentendienste.

  7. Beenden Sie SQL Server, und starten Sie die Plattform dann neu, um sicherzustellen, dass die MS DTC-Änderungen synchronisiert werden. (Dieser Schritt ist optional für SQL Server 2019 und SQL Server 2017 CU 16 und höher.)

Konfigurieren der JDBC-Komponenten für verteilte Transaktionen

Die Schritte zum Konfigurieren serverseitiger Komponenten unterscheiden sich je nach Zielserverversion. Um die Serverversion zu überprüfen, führen Sie die Abfrage SELECT @@VERSION für den Server aus, und zeigen Sie die Ausgabe an. Befolgen Sie für das kumulative SQL Server 2017-Update 16 und höher die Anweisungen unter SQL Server 2017 CU16 und höher. Befolgen Sie für ältere SQL Server-Versionen die Anweisungen unter SQL Server 2017 CU15 und früher.

SQL Server 2017 CU16 und höher

Führen Sie die folgende gespeicherte Prozedur aus, um die erforderlichen Komponenten zum Durchführen von verteilten XA-Transaktionen mit dem JDBC-Treiber zu aktivieren.

EXEC sp_sqljdbc_xa_install

Führen Sie die folgende gespeicherte Prozedur aus, um die Komponenten zu deaktivieren.

EXEC sp_sqljdbc_xa_uninstall

Fahren Sie mit dem Abschnitt Konfigurieren von serverseitigen Timeouteinstellungen für den automatischen Rollback nicht vorbereiteter Transaktionen fort.

SQL Server 2017 CU15 und früher

Hinweis

Dies gilt nur für SQL Server 2017 CU15 und niedriger. Die von sqljdbc_xa.dll bereitgestellten Funktionen sind in SQL Server 2017 CU16 und höher bereits enthalten.

Die JDBC-Komponenten für verteilte Transaktionen sind im Verzeichnis "xa" der JDBC-Treiberinstallation enthalten. Zu diesen Komponenten zählen die Dateien "xa_install.sql" und "sqljdbc_xa.dll". Wenn Sie auf verschiedenen Clients verschiedene Versionen des JDBC-Treibers installiert haben, empfiehlt sich die Verwendung der aktuellen sqljdbc_xa.dll-Datei auf dem Server.

Führen Sie die folgenden Schritte aus, um die Komponenten des JDBC-Treibers für verteilte Transaktionen zu konfigurieren:

  1. Kopieren Sie die neue Datei „sqljdbc_xa.dll“ aus dem Installationsverzeichnis des JDBC-Treibers in das Verzeichnis „Binn“ auf allen SQL Server-Computern, die an verteilten Transaktionen teilnehmen.

    Hinweis

    Wenn Sie XA-Transaktionen mit einer 32-Bit-Instanz von SQL Server nutzen (gilt nur für SQL Server 2014 und früher), verwenden Sie die Datei „sqljdbc_xa.dll“ im Ordner „x86“, auch wenn SQL Server auf einem x64-Prozessor installiert ist. Wenn Sie XA-Transaktionen mit einem 64-Bit-SQL Server auf dem x64-Prozessor verwenden, verwenden Sie die Datei „sqljdbc_xa.dll“ im Ordner „x64“.

  2. Führen Sie auf allen SQL Server-Instanzen, die an verteilten Transaktionen teilnehmen, das Datenbankskript „xa_install.sql“ aus. Mit diesem Skript werden die erweiterten gespeicherten Prozeduren installiert, die von sqljdbc_xa.dll aufgerufen werden. Diese erweiterten gespeicherten Prozeduren implementieren die Unterstützung für verteilte Transaktionen und XA für Microsoft JDBC-Treiber für SQL Server. Sie müssen dieses Skript als Administrator*in der SQL Server-Instanz ausführen.

  3. Um einem bestimmten Benutzer die Berechtigungen für die Teilnahme an verteilten Transaktionen mit dem JDBC-Treiber zu gewähren, fügen Sie den Benutzer der Rolle "SqlJDBCXAUser" hinzu.

Sie können auf jeder SQL Server-Instanz nur eine Version der Assembly „sqljdbc_xa.dll“ gleichzeitig konfigurieren. Anwendungen müssen möglicherweise unterschiedliche Versionen des JDBC-Treibers verwenden, um mithilfe der XA-Verbindung eine Verbindung mit der gleichen SQL Server-Instanz herzustellen. In diesem Fall muss die Datei „sqljdbc_xa.dll“, die Teil des neuesten JDBC-Treibers ist, auf der SQL Server-Instanz installiert werden.

Sie können die derzeit auf der SQL Server-Instanz installierte Version von „sqljdbc_xa.dll“ auf drei Arten überprüfen:

  1. Öffnen Sie das Verzeichnis „LOG“ des SQL Server-Computers, der an verteilten Transaktionen teilnimmt. Wählen Sie die SQL Server-Datei „ERRORLOG“ aus, und öffnen Sie diese. Suchen Sie in der Datei "ERRORLOG" nach "Version ... von 'SQLJDBC_XA.dll' wird verwendet".

  2. Öffnen Sie das Verzeichnis „Binn“ des SQL Server-Computers, der an verteilten Transaktionen teilnimmt. Wählen Sie die Assembly „sqljdbc_xa.dll“ aus.

    • Unter Windows Vista oder höher: Klicken Sie mit der rechten Maustaste auf „sqljdbc_xa.dll“, und wählen Sie dann „Eigenschaften“ aus. Klicken Sie dann auf die Registerkarte Details. Im Feld Dateiversion wird die Version von „sqljdbc_xa.dll“ angezeigt, die derzeit auf der SQL Server-Instanz installiert ist.
  3. Legen Sie die Protokollfunktion wie im Codebeispiel im nächsten Abschnitt fest. Suchen Sie in der Ausgabeprotokolldatei nach "Server-XA-DLL-Version ...".

Aktualisieren von "sqljdbc_xa.dll"

Hinweis

Dies gilt nur für SQL Server 2017 CU15 und niedriger. Die von sqljdbc_xa.dll bereitgestellten Funktionen sind in SQL Server 2017 CU16 und höher bereits enthalten.

Wenn Sie eine neue Version des JDBC-Treibers installieren, sollten Sie auch sqljdbc_xa.dll aus der neuen Version verwenden, um sqljdbc_xa.dll auf dem Server zu aktualisieren.

Wichtig

Sie sollten das Upgrade der Datei „sqljdbc_xa.dll“ im Rahmen einer Wartung oder zu einem Zeitpunkt ausführen, zu dem keine MS DTC-Transaktionen in Bearbeitung sind.

  1. Entladen Sie „sqljdbc_xa.dll“ mit folgendem Transact-SQL-Befehl:

    DBCC sqljdbc_xa (FREE)
    
  2. Kopieren Sie die neue Datei „sqljdbc_xa.dll“ aus dem Installationsverzeichnis des JDBC-Treibers in das Verzeichnis „Binn“ auf allen SQL Server-Computern, die an verteilten Transaktionen teilnehmen.

    Die neue DLL wird geladen, wenn eine erweiterte Prozedur in „sqljdbc_xa.dll“ aufgerufen wird. Sie müssen SQL Server nicht neu starten, um die neuen Definitionen zu laden.

Konfigurieren von serverseitigen Timeouteinstellungen für den automatischen Rollback nicht vorbereiteter Transaktionen

Das Timeoutverhalten von verteilten Transaktionen wird mithilfe von zwei Registrierungseinstellungen (DWORD-Werten) gesteuert:

  • XADefaultTimeout (in Sekunden): Der zu verwendende Standardwert für das Timeout, wenn der/die Benutzer*in kein Timeout angibt. Die Standardeinstellung ist 0.

  • XAMaxTimeout (in Sekunden): Der Höchstwert, den Benutzer für das Zeitlimit festlegen können Die Standardeinstellung ist 0.

Diese Einstellungen sind für die jeweilige SQL Server-Instanz spezifisch und sollten unter diesem Registrierungsschlüssel erstellt werden:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout

Hinweis

Für 32-Bit-Installationen von SQL Server, die auf 64-Bit-Computern ausgeführt werden (gilt nur für SQL Server 2014 und früher), sollten die Registrierungseinstellungen unter diesem Schlüssel erstellt werden: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout

Für jede Transaktion wird bei ihrem Start ein Timeoutwert festgelegt, und SQL Server führt ein Rollback der Transaktion durch, wenn das Timeout abläuft. Das Timeout wird auf der Grundlage dieser Registrierungseinstellungen und der Angaben des Benutzers in „XAResource.setTransactionTimeout()“ festgelegt. Hier einige Beispiele zur Interpretation dieser Timeoutwerte:

  • XADefaultTimeout = 0, XAMaxTimeout = 0

    Dies bedeutet, dass kein Standardtimeout verwendet und kein maximales Timeout für Clients durchgesetzt wird. In diesem Fall liegt ein Timeout für Transaktionen nur dann vor, wenn der Client ein Timeout über „XAResource.setTransactionTimeout“ festlegt.

  • XADefaultTimeout = 60, XAMaxTimeout = 0

    Dies bedeutet, dass bei allen Transaktionen nach 60 Sekunden ein Timeout eintritt, wenn vom Client kein Timeout festgelegt wird. Wenn der Client ein Timeout angibt, wird dieser Timeoutwert verwendet. Es wird kein maximaler Wert für das Timeout durchgesetzt.

  • XADefaultTimeout = 30, XAMaxTimeout = 60

    Dies bedeutet, dass bei allen Transaktionen nach 30 Sekunden ein Timeout eintritt, wenn vom Client kein Timeout festgelegt wird. Wenn der Client ein Timeout angibt, wird das Timeout des Clients verwendet, sofern es weniger als 60 Sekunden (den Maximalwert) beträgt.

  • XADefaultTimeout = 0, XAMaxTimeout = 30

    Dies bedeutet, dass bei allen Transaktionen nach 30 Sekunden (dem Maximalwert) ein Timeout auftritt, wenn vom Client kein Timeout festgelegt wird. Wenn der Client ein Timeout angibt, wird das Timeout des Clients verwendet, sofern es weniger als 30 Sekunden (den Maximalwert) beträgt.

Konfigurieren der benutzerdefinierten Rollen

Um einem bestimmten Benutzer die Berechtigungen für die Teilnahme an verteilten Transaktionen mit dem JDBC-Treiber zu gewähren, fügen Sie den Benutzer der Rolle "SqlJDBCXAUser" hinzu. Verwenden Sie z. B. den folgenden Transact-SQL-Code, um der Rolle „SqlJDBCXAUser“ einen Benutzer mit dem Namen „shelly“ (Benutzer namens „shelly“, der die SQL-Standardanmeldung verwendet) hinzuzufügen:

USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'

Benutzerdefinierte SQL-Rollen werden jeweils für eine Datenbank definiert. Wenn Sie aus Sicherheitsgründen eigene Rollen erstellen möchten, müssen Sie die Rolle in jeder Datenbank definieren und die Benutzer*innen den jeweiligen Datenbanken hinzufügen. Die Rolle "SqlJDBCXAUser" ist ausschließlich in der Masterdatenbank definiert, da damit Zugriff auf die erweiterten gespeicherten SQL JDBC-Prozeduren gewährt wird, die sich in der Masterdatenbank befinden. Sie müssen den einzelnen Benutzer*innen zuerst Zugriff auf die Masterdatenbank und dann auf die Rolle „SqlJDBCXAUser“ gewähren. Dazu müssen Sie bei der Masterdatenbank angemeldet sein.

Beispiel

import java.net.Inet4Address;
import java.sql.*;
import java.util.Random;
import javax.sql.XAConnection;
import javax.transaction.xa.*;
import com.microsoft.sqlserver.jdbc.*;


public class testXA {

    public static void main(String[] args) throws Exception {

        // Create variables for the connection string.
        String prefix = "jdbc:sqlserver://";
        String serverName = "localhost";
        int portNumber = 1433;
        String databaseName = "AdventureWorks";
        String user = "UserName";
        String password = "*****";

        String connectionUrl = prefix + serverName + ":" + portNumber + ";encrypt=true;databaseName=" + databaseName + ";user="
                + user + ";password=" + password;

        Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");

        try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement()) {
            stmt.executeUpdate("CREATE TABLE XAMin (f1 int, f2 varchar(max))");

        }
        // Create the XA data source and XA ready connection.
        SQLServerXADataSource ds = new SQLServerXADataSource();
        ds.setUser(user);
        ds.setPassword(password);
        ds.setServerName(serverName);
        ds.setPortNumber(portNumber);
        ds.setDatabaseName(databaseName);

        XAConnection xaCon = ds.getXAConnection();
        try (Connection con = xaCon.getConnection()) {

            // Get a unique Xid object for testing.
            XAResource xaRes = null;
            Xid xid = null;
            xid = XidImpl.getUniqueXid(1);

            // Get the XAResource object and set the timeout value.
            xaRes = xaCon.getXAResource();
            xaRes.setTransactionTimeout(0);

            // Perform the XA transaction.
            System.out.println("Write -> xid = " + xid.toString());
            xaRes.start(xid, XAResource.TMNOFLAGS);
            PreparedStatement pstmt = con.prepareStatement("INSERT INTO XAMin (f1,f2) VALUES (?, ?)");
            pstmt.setInt(1, 1);
            pstmt.setString(2, xid.toString());
            pstmt.executeUpdate();

            // Commit the transaction.
            xaRes.end(xid, XAResource.TMSUCCESS);
            xaRes.commit(xid, true);
        }
        xaCon.close();

        // Open a new connection and read back the record to verify that it worked.
        try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery("SELECT * FROM XAMin")) {
            rs.next();
            System.out.println("Read -> xid = " + rs.getString(2));
            stmt.executeUpdate("DROP TABLE XAMin");
        }
    }
}


class XidImpl implements Xid {

    public int formatId;
    public byte[] gtrid;
    public byte[] bqual;

    public byte[] getGlobalTransactionId() {
        return gtrid;
    }

    public byte[] getBranchQualifier() {
        return bqual;
    }

    public int getFormatId() {
        return formatId;
    }

    XidImpl(int formatId, byte[] gtrid, byte[] bqual) {
        this.formatId = formatId;
        this.gtrid = gtrid;
        this.bqual = bqual;
    }

    public String toString() {
        int hexVal;
        StringBuffer sb = new StringBuffer(512);
        sb.append("formatId=" + formatId);
        sb.append(" gtrid(" + gtrid.length + ")={0x");
        for (int i = 0; i < gtrid.length; i++) {
            hexVal = gtrid[i] & 0xFF;
            if (hexVal < 0x10)
                sb.append("0" + Integer.toHexString(gtrid[i] & 0xFF));
            else
                sb.append(Integer.toHexString(gtrid[i] & 0xFF));
        }
        sb.append("} bqual(" + bqual.length + ")={0x");
        for (int i = 0; i < bqual.length; i++) {
            hexVal = bqual[i] & 0xFF;
            if (hexVal < 0x10)
                sb.append("0" + Integer.toHexString(bqual[i] & 0xFF));
            else
                sb.append(Integer.toHexString(bqual[i] & 0xFF));
        }
        sb.append("}");
        return sb.toString();
    }

    // Returns a globally unique transaction id.
    static byte[] localIP = null;
    static int txnUniqueID = 0;

    static Xid getUniqueXid(int tid) {

        Random rnd = new Random(System.currentTimeMillis());
        txnUniqueID++;
        int txnUID = txnUniqueID;
        int tidID = tid;
        int randID = rnd.nextInt();
        byte[] gtrid = new byte[64];
        byte[] bqual = new byte[64];
        if (null == localIP) {
            try {
                localIP = Inet4Address.getLocalHost().getAddress();
            } catch (Exception ex) {
                localIP = new byte[] {0x01, 0x02, 0x03, 0x04};
            }
        }
        System.arraycopy(localIP, 0, gtrid, 0, 4);
        System.arraycopy(localIP, 0, bqual, 0, 4);

        // Bytes 4 -> 7 - unique transaction id.
        // Bytes 8 ->11 - thread id.
        // Bytes 12->15 - random number generated by using seed from current time in milliseconds.
        for (int i = 0; i <= 3; i++) {
            gtrid[i + 4] = (byte) (txnUID % 0x100);
            bqual[i + 4] = (byte) (txnUID % 0x100);
            txnUID >>= 8;
            gtrid[i + 8] = (byte) (tidID % 0x100);
            bqual[i + 8] = (byte) (tidID % 0x100);
            tidID >>= 8;
            gtrid[i + 12] = (byte) (randID % 0x100);
            bqual[i + 12] = (byte) (randID % 0x100);
            randID >>= 8;
        }
        return new XidImpl(0x1234, gtrid, bqual);
    }
}

Weitere Informationen

Ausführen von Transaktionen mit dem JDBC-Treiber