Come accedere a SQL Server nel contesto di una transazione XA con il driver ODBC di Easysoft SQL Server e Oracle Tuxedo.
Introduzione
Perché sono necessarie le transazioni distribuite
Una transazione è una serie di azioni eseguite come una singola operazione in cui vengono eseguite tutte le azioni o nessuna di esse. Una transazione termina con un'azione di commit che rende permanenti le modifiche. Se non è possibile eseguire il commit di una qualsiasi delle modifiche, la transazione verrà ripristinata, annullando tutte le modifiche.
Una transazione distribuita è una transazione che può estendersi su più risorse. Ad esempio, uno o più database o un database e una coda di messaggi. Affinché la transazione si impegni correttamente, tutte le singole risorse devono impegnarsi correttamente; se qualcuno di essi non riesce, la transazione deve eseguire il rollback di tutte le risorse. Ad esempio, una transazione distribuita potrebbe consistere in un trasferimento di denaro tra due conti bancari, ospitati da banche diverse, e quindi anche su database diversi. Non vorresti che nessuna transazione fosse impegnata senza la garanzia che entrambe si completeranno con successo. In caso contrario, i dati potrebbero essere duplicati (se l'inserimento viene completato e l'eliminazione non riesce) o persi (se l'eliminazione viene completata e l'inserimento non riesce).
Pertanto, ogni volta che un'applicazione deve accedere o aggiornare i dati in più risorse transazionali, dovrebbe utilizzare una transazione distribuita. È possibile utilizzare una transazione separata su ciascuna delle risorse, ma questo approccio è soggetto a errori. Se la transazione in una risorsa viene eseguita correttamente ma un'altra ha esito negativo e deve essere eseguito il rollback, non è più possibile eseguire il rollback della prima transazione, quindi lo stato dell'applicazione diventa incoerente. Se una risorsa viene eseguita correttamente ma il sistema si arresta in modo anomalo prima che l'altra risorsa possa eseguire correttamente il commit, l'applicazione è di nuovo incoerente.
XA
Il modello X/Open Distributed Transaction Processing (DTP) definisce un'architettura per l'elaborazione delle transazioni distribuite. Nell'architettura DTP, un gestore delle transazioni di coordinamento indica a ciascuna risorsa come elaborare una transazione, in base alla sua conoscenza di tutte le risorse che partecipano alla transazione. Le risorse che normalmente gestiscono il commit e il ripristino delle proprie transazioni delegano questa attività al gestore delle transazioni.
La specifica XA dell'architettura fornisce uno standard aperto che garantisce l'interoperabilità tra middleware transazionale conforme e prodotti di database. Queste diverse risorse sono quindi in grado di partecipare insieme a una transazione distribuita.
Il modello DTP include tre componenti correlati:
- Un programma applicativo che definisce i limiti di transazione e specifica le azioni che costituiscono una transazione.
- Gestione risorse come database o file system che forniscono l'accesso alle risorse condivise.
- Un Transaction Manager che assegna identificatori alle transazioni, ne monitora l'andamento e si assume la responsabilità del completamento delle transazioni e del ripristino degli errori.
Lo standard XA definisce il protocollo di commit a due fasi e l'interfaccia utilizzata per la comunicazione tra un Transaction Manager e un Resource Manager. Il protocollo di commit in due fasi fornisce una garanzia "tutto o niente" che tutti i partecipanti coinvolti nella transazione eseguiranno il commit o il rollback insieme. L'intera transazione esegue il commit o l'intera transazione viene ripristinata, quindi.
Il commit a due fasi consiste in una fase di preparazione e una fase di commit. Durante la fase di preparazione, tutti i partecipanti alla transazione devono accettare di completare le modifiche richieste dalla transazione. Se uno dei partecipanti segnala un problema, la fase di preparazione avrà esito negativo e la transazione verrà annullata. Se la fase di preparazione ha esito positivo, fase due, inizia la fase di commit. Durante la fase di commit, il Transaction Manager istruisce tutti i partecipanti a eseguire il commit della transazione.
SQL Server e XA
Per abilitare il supporto XA in SQL Server 2019, segui le istruzioni nella sezione "Esecuzione del servizio MS DTC" contenuta in questo documento:
Comprensione delle transazioni XA
Per abilitare il supporto XA nelle versioni precedenti di SQL Server, segui le istruzioni in questo documento:
Configurazione delle transazioni XA in Microsoft SQL Server per IBM Business Process Manager (BPM)
Il driver ODBC di SQL Server è stato testato con istanze di SQL Server 2016 e 2019 abilitate per XA.
Il driver ODBC di Easysoft SQL Server
Il supporto XA è stato aggiunto al driver ODBC di SQL Server nella versione 1.11.3. Il supporto XA del driver è stato testato con Oracle Tuxedo e SQL Server 2016 e 2019.
Per integrare il driver ODBC di SQL Server in una transazione XA, è necessario utilizzare una struttura denominata es_xa_context
nella tua applicazione. es_xa_context
si connette all'origine dati ODBC specificata nella configurazione del gestore risorse XA e restituisce un handle di connessione. Ad esempio:
int ret; SQLHANDLE hEnv, hConn; ret = es_xa_context( NULL, &hEnv, &hConn );
In Tuxedo, l'origine dati ODBC che es_xa_context
si connette è specificato in Gestione risorse OPENINFO
stringa nel file di configurazione di Tuxedo. In questo esempio, è "SQLSERVER_SAMPLE":
OPENINFO="EASYSOFT_SQLSERVER_ODBC:DSN=SQLSERVER_SAMPLE"
Il nome di XA Resource Manager e l'opzione XA definiti dal driver sono EASYSOFT_SQLSERVER_ODBC
e essql_xaosw
.
In Tuxedo, li specifichi nel file di definizione di Tuxedo Resource Manager, ${TUXDIR}/udataobj/RM
. Ad esempio:
EASYSOFT_SQLSERVER_ODBC:essql_xaosw:-L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbcinst
Esempio di applicazione Easysoft / Tuxedo / SQL Server XA
Innanzitutto, configura un'origine dati del driver ODBC di SQL Server che si connette a un'istanza di SQL Server abilitata per XA:
- Sul tuo computer Tuxedo, installa il driver ODBC di SQL Server.
- Crea un'origine dati del driver ODBC di SQL Server in odbc.ini. Ad esempio:
[SQLSERVER_SAMPLE] Driver=Easysoft ODBC-SQL Server Description=Easysoft SQL Server ODBC driver Server=mymachine\myxaenabledinstance User=mydomain\myuser Password=mypassword Database=XA1
- Crea una tabella di esempio per l'applicazione Tuxedo:
$ /usr/local/easysoft/unixODBC/bin/isql.sh -v SQLSERVER_SAMPLE SQL> CREATE TABLE [dbo].[tx_test1]([i] [int] NULL,[c] [varchar](100) NULL)
Crea ed esegui l'applicazione di esempio Tuxedo XA.
-
$ cd ~ $ mkdir simpdir $ cd simpdir $ touch simpcl.c simpserv.c ubbsimple
- Aggiungi queste righe a simpcl.c:
#include <stdio.h> #include "atmi.h" /* TUXEDO Header File */ #if defined(__STDC__) || defined(__cplusplus) main(int argc, char *argv[]) #else main(argc, argv) int argc; char *argv[]; #endif { char *sendbuf, *rcvbuf; long sendlen, rcvlen; int ret; if(argc != 2) { (void) fprintf(stderr, "Usage: simpcl <SQL>\n"); exit(1); } /* Attach to System/T as a Client Process */ if (tpinit((TPINIT *) NULL) == -1) { (void) fprintf(stderr, "Tpinit failed\n"); exit(1); } sendlen = strlen(argv[1]); /* Allocate STRING buffers for the request and the reply */ if((sendbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) { (void) fprintf(stderr,"Error allocating send buffer\n"); tpterm(); exit(1); } if((rcvbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) { (void) fprintf(stderr,"Error allocating receive buffer\n"); tpfree(sendbuf); tpterm(); exit(1); } (void) strcpy(sendbuf, argv[1]); /* Request the service EXECUTE, waiting for a reply */ ret = tpcall("EXECUTE", (char *)sendbuf, 0, (char **)&rcvbuf, &rcvlen, (long)0); if(ret == -1) { (void) fprintf(stderr, "Can't send request to service EXECUTE\n"); (void) fprintf(stderr, "Tperrno = %d\n", tperrno); tpfree(sendbuf); tpfree(rcvbuf); tpterm(); exit(1); } (void) fprintf(stdout, "Returned string is: %s\n", rcvbuf); /* Free Buffers & Detach from System/T */ tpfree(sendbuf); tpfree(rcvbuf); tpterm(); return(0); }
- Aggiungi queste righe a simpserv.c:
#include <stdio.h> #include <ctype.h> #include <atmi.h> /* TUXEDO Header File */ #include <userlog.h> /* TUXEDO Header File */ #include <xa.h> #include <sql.h> #include <sqlext.h> #include <string.h> /* tpsvrinit is executed when a server is booted, before it begins processing requests. It is not necessary to have this function. Also available is tpsvrdone (not used in this example), which is called at server shutdown time. */ int tpsvrinit(int argc, char *argv[]) { int ret; /* Some compilers warn if argc and argv aren't used. */ argc = argc; argv = argv; /* simpapp is non-transactional, so there is no need for tpsvrinit() to call tx_open() or tpopen(). However, if this code is modified to run in a Tuxedo group associated with a Resource Manager then either a call to tx_open() or a call to tpopen() must be inserted here. */ /* userlog writes to the central TUXEDO message log */ userlog("Welcome to the simple server"); ret = tpopen(); userlog("tpopen returned %d, error=%x", ret, tperrno ); return(0); } void tpsvrdone( void ) { int ret; ret = tpclose(); userlog("tpclose returned %d", ret); } /* This function performs the actual service requested by the client. Its argument is a structure containing among other things a pointer to the data buffer, and the length of the data buffer. */ xa_open_entry() call. int es_xa_context( int* rmid, SQLHANDLE* henv, SQLHANDLE* hdbc ); void EXECUTE(TPSVCINFO *rqst) { int ret; char *result; SQLHANDLE hStmt; char str[ 256 ]; SQLHANDLE hEnv, hConn; SQLSMALLINT slen; ret = es_xa_context( NULL, &hEnv, &hConn ); userlog("es_xa_context returns %d, hEnv = %p, hConn = %p", ret, hEnv, hConn ); if ( ret != 0 ) { result = tpalloc( "STRING", "*", 128 ); sprintf( result, "es_xa_context returned %d", ret ); /* Return the transformed buffer to the requestor. */ tpreturn(TPSUCCESS, 0, result, strlen( result ), 0); } else { ret = tpbegin( 0, 0 ); ret = SQLAllocHandle( SQL_HANDLE_STMT, hConn, &hStmt ); ret = SQLExecDirect( hStmt, rqst -> data, rqst -> len ); ret = SQLFreeHandle( SQL_HANDLE_STMT, hStmt ); ret = tpcommit( 0 ); result = tpalloc( "STRING", "*", 128 ); sprintf( result, "tpcommit returns %d", ret ); /* Return the transformed buffer to the requestor. */ tpreturn(TPSUCCESS, 0, result, strlen( result ), 0); } }
- Aggiungi queste righe a ubbsimple:
*RESOURCES IPCKEY 123456 DOMAINID simpapp MASTER simple MAXACCESSERS 20 MAXSERVERS 10 MAXSERVICES 10 MODEL SHM LDBAL N *MACHINES DEFAULT: APPDIR="/home/myuser/simpdir" TUXCONFIG="/home/myuser/simpdir/tuxconfig" TUXDIR="/home/myuser/OraHome/tuxedo12.2.2.0.0" mymachine LMID=simple TLOGNAME=TLOG TLOGDEVICE="/home/myuser/simpdir/tuxlog" *GROUPS GROUP1 LMID=simple GRPNO=1 OPENINFO=NONE TMSNAME=mySQLSERVER_TMS OPENINFO="EASYSOFT_SQLSERVER_ODBC:DSN=SQLSERVER_SAMPLE" *SERVERS DEFAULT: CLOPT="-A" simpserv SRVGRP=GROUP1 SRVID=1 *SERVICES EXECUTE
- Imposta il tuo ambiente:
export TUXDIR=/home/myuser/OraHome/tuxedo12.2.2.0.0 export TUXCONFIG=/home/myuser/simpdir/tuxconfig export PATH=$PATH:$TUXDIR/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$TUXDIR/lib:/usr/local/easysoft/unixODBC/lib: \ /usr/local/easysoft/sqlserver/lib:/usr/local/easysoft/lib
- Crea il client di esempio:
buildclient -o simpcl -f simpcl.c
Se ricevi l'errore "riferimento non definito a dlopen" durante la creazione del client, prova invece questo comando:
buildclient -o simpcl -f "-Xlinker --no-as-needed simpcl.c"
- Crea il server di esempio:
buildserver -r EASYSOFT_SQLSERVER_ODBC -s EXECUTE -o simpserv -f "simpserv.c \ -L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbc"
- Crea il file TUXCONFIG per l'applicazione di esempio:
tmloadcf ubbsimple
- Crea un dispositivo di registrazione Tuxedo per l'applicazione di esempio:
$ tmadmin -c > crdl -z /home/myuser/simpdir/tuxlog -b 512
- Crea un gestore delle transazioni Tuxedo che si interfaccia con il driver ODBC di SQL Server:
$ buildtms -o mySQLSERVER_TMS -r EASYSOFT_SQLSERVER_ODBC
- Avvia il server di esempio:
$ tmboot
- Testare l'applicazione di esempio:
./simpcl "insert into tx_test1 values( 1, 'hello world' )" /usr/local/easysoft/unixODBC/bin/isql.sh -v SQLSERVER_SAMPLE SQL> select * from tx_test1 +------------+--------------+ | i | c | +------------+--------------+ | 1 | hello world | +------------+--------------+
- Se vedi i dati nella tabella di SQL Server, spegni il server di esempio:
tmshutdown
Altrimenti, consulta ULOG.nnn nella directory dell'applicazione di esempio.