Jak uzyskać dostęp do SQL Server w kontekście transakcji XA za pomocą sterownika ODBC Easysoft SQL Server i Oracle Tuxedo.
Wprowadzenie
Dlaczego potrzebne są transakcje rozproszone
Transakcja to seria czynności wykonywanych jako pojedyncza operacja, w której wykonywane są wszystkie czynności lub żadna z nich nie jest wykonywana. Transakcja kończy się akcją zatwierdzania, która sprawia, że zmiany są trwałe. Jeśli którakolwiek ze zmian nie może zostać zatwierdzona, transakcja zostanie wycofana, cofając wszystkie zmiany.
Transakcja rozproszona to transakcja, która może obejmować wiele zasobów. Na przykład co najmniej jedna baza danych lub baza danych i kolejka komunikatów. Aby transakcja została pomyślnie zatwierdzona, wszystkie poszczególne zasoby muszą zostać pomyślnie zatwierdzone; jeśli którykolwiek z nich nie powiedzie się, transakcja musi zostać wycofana we wszystkich zasobach. Na przykład transakcja rozproszona może polegać na przekazie pieniężnym między dwoma rachunkami bankowymi prowadzonymi przez różne banki, a więc także w różnych bazach danych. Nie chciałbyś, aby żadna transakcja została popełniona bez gwarancji, że obie zakończą się pomyślnie. W przeciwnym razie dane mogą zostać zduplikowane (jeśli wstawianie zakończy się i usunięcie się nie powiedzie) lub utracone (jeśli usunięcie zakończy się, a wstawienie się nie powiedzie).
Ilekroć aplikacja potrzebuje dostępu lub aktualizacji danych w wielu zasobach transakcyjnych, powinna używać transakcji rozproszonych. Możliwe jest użycie oddzielnej transakcji na każdym z zasobów, ale takie podejście jest podatne na błędy. Jeśli transakcja w jednym zasobach zostanie pomyślnie zatwierdzona, ale inna nie powiedzie się i musi zostać wycofana, nie można już wycofać pierwszej transakcji, więc stan aplikacji staje się niespójny. Jeśli jeden zasób zostanie pomyślnie zatwierdzony, ale system ulegnie awarii przed pomyślnym zatwierdzeniem innego zasobu, aplikacja ponownie jest niespójna.
XA
Model X/Open Distributed Transaction Processing (DTP) definiuje architekturę przetwarzania transakcji rozproszonych. W architekturze DTP koordynujący menedżer transakcji instruuje każdy zasób, jak przetworzyć transakcję, na podstawie swojej wiedzy o wszystkich zasobach uczestniczących w transakcji. Zasoby, które zwykle zarządzają własnymi zatwierdzaniem transakcji i odzyskiwaniem, delegują to zadanie do menedżera transakcji.
Specyfikacja architektury XA zapewnia otwarty standard, który zapewnia współdziałanie zgodnego transakcyjnego oprogramowania pośredniego i produktów bazodanowych. Te różne zasoby są zatem w stanie wspólnie uczestniczyć w rozproszonej transakcji.
Model DTP zawiera trzy powiązane ze sobą komponenty:
- Program aplikacyjny, który definiuje granice transakcji i określa działania, które stanowią transakcję.
- Menedżerowie zasobów, tacy jak bazy danych lub systemy plików, które zapewniają dostęp do współdzielonych zasobów.
- Menedżer transakcji, który przypisuje identyfikatory do transakcji, monitoruje ich postęp i bierze odpowiedzialność za zakończenie transakcji i naprawę awarii.
Standard XA definiuje protokół zatwierdzania dwufazowego i interfejs używany do komunikacji między menedżerem transakcji a menedżerem zasobów. Protokół zatwierdzania dwufazowego zapewnia gwarancję „wszystko albo nic”, że wszyscy uczestnicy zaangażowani w transakcję albo zatwierdzą, albo wycofają się razem. W związku z tym cała transakcja zostaje zatwierdzona lub cała transakcja jest wycofywana.
Zatwierdzenie dwufazowe składa się z fazy przygotowania i fazy zatwierdzenia. W fazie przygotowania wszyscy uczestnicy transakcji muszą wyrazić zgodę na uzupełnienie zmian wymaganych przez transakcję. Jeśli którykolwiek z uczestników zgłosi problem, faza przygotowania zakończy się niepowodzeniem i transakcja zostanie wycofana. Jeśli faza przygotowania się powiedzie, faza druga, rozpoczyna się faza zatwierdzania. Podczas fazy zatwierdzania Menedżer Transakcji instruuje wszystkich uczestników, aby zatwierdzili transakcję.
Serwer SQL i XA
Aby włączyć obsługę XA w SQL Server 2019, postępuj zgodnie z instrukcjami w sekcji „Uruchamianie usługi MS DTC” zawartej w tym dokumencie:
Zrozumienie transakcji XA
Aby włączyć obsługę XA we wcześniejszych wersjach SQL Server, postępuj zgodnie z instrukcjami w tym dokumencie:
Konfigurowanie transakcji XA w Microsoft SQL Server dla IBM Business Process Manager (BPM)
Sterownik ODBC SQL Server został przetestowany z instancjami SQL Server 2016 i 2019 obsługującymi XA.
Sterownik ODBC Easysoft SQL Server
Obsługa XA została dodana do sterownika ODBC programu SQL Server w wersji 1.11.3. Obsługa XA sterownika została przetestowana z Oracle Tuxedo i SQL Server 2016 i 2019.
Aby włączyć sterownik ODBC SQL Server do transakcji XA, musisz użyć struktury o nazwie es_xa_context
w Twojej aplikacji. es_xa_context
łączy się ze źródłem danych ODBC określonym w konfiguracji menedżera zasobów XA i zwraca uchwyt połączenia. Na przykład:
int ret; SQLHANDLE hEnv, hConn; ret = es_xa_context( NULL, &hEnv, &hConn );
W Tuxedo źródło danych ODBC, które es_xa_context
łączy się z jest określone w Menedżerze zasobów OPENINFO
w pliku konfiguracyjnym Tuxedo. W tym przykładzie jest to „SQLSERVER_SAMPLE”:
OPENINFO="EASYSOFT_SQLSERVER_ODBC:DSN=SQLSERVER_SAMPLE"
Zdefiniowana przez sterownik nazwa menedżera zasobów XA i przełącznik XA to EASYSOFT_SQLSERVER_ODBC
i essql_xaosw
.
W Tuxedo określasz je w pliku definicji Tuxedo Resource Manager, ${TUXDIR}/udataobj/RM
. Na przykład:
EASYSOFT_SQLSERVER_ODBC:essql_xaosw:-L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbcinst
Przykład aplikacji Easysoft / Tuxedo / SQL Server XA
Najpierw skonfiguruj źródło danych sterownika ODBC SQL Server, które łączy się z instancją SQL Server z obsługą XA:
- Na komputerze Tuxedo zainstaluj sterownik ODBC SQL Server.
- Utwórz źródło danych sterownika ODBC programu SQL Server w pliku odbc.ini. Na przykład:
[SQLSERVER_SAMPLE] Driver=Easysoft ODBC-SQL Server Description=Easysoft SQL Server ODBC driver Server=mymachine\myxaenabledinstance User=mydomain\myuser Password=mypassword Database=XA1
- Utwórz przykładową tabelę dla aplikacji Tuxedo:
$ /usr/local/easysoft/unixODBC/bin/isql.sh -v SQLSERVER_SAMPLE SQL> CREATE TABLE [dbo].[tx_test1]([i] [int] NULL,[c] [varchar](100) NULL)
Utwórz i uruchom przykładową aplikację Tuxedo XA.
-
$ cd ~ $ mkdir simpdir $ cd simpdir $ touch simpcl.c simpserv.c ubbsimple
- Dodaj te wiersze do 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); }
- Dodaj te wiersze do 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); } }
- Dodaj te wiersze do 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
- Ustaw środowisko:
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
- Zbuduj przykładowego klienta:
buildclient -o simpcl -f simpcl.c
Jeśli podczas budowania klienta pojawi się błąd „undefined reference to dlopen”, wypróbuj zamiast tego następujące polecenie:
buildclient -o simpcl -f "-Xlinker --no-as-needed simpcl.c"
- Zbuduj przykładowy serwer:
buildserver -r EASYSOFT_SQLSERVER_ODBC -s EXECUTE -o simpserv -f "simpserv.c \ -L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbc"
- Utwórz plik TUXCONFIG dla przykładowej aplikacji:
tmloadcf ubbsimple
- Utwórz urządzenie rejestrujące Tuxedo dla przykładowej aplikacji:
$ tmadmin -c > crdl -z /home/myuser/simpdir/tuxlog -b 512
- Zbuduj menedżera transakcji Tuxedo, który współpracuje ze sterownikiem ODBC SQL Server:
$ buildtms -o mySQLSERVER_TMS -r EASYSOFT_SQLSERVER_ODBC
- Uruchom przykładowy serwer:
$ tmboot
- Przetestuj przykładową aplikację:
./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 | +------------+--------------+
- Jeśli widzisz dane w tabeli SQL Server, zamknij przykładowy serwer:
tmshutdown
W przeciwnym razie sprawdź ULOG.nnn w katalogu przykładowej aplikacji.