了解 XA 交易
Microsoft JDBC Driver for SQL Server 提供對 Java 平台企業版/JDBC 2.0 選擇性分散式交易的支援。 從 SQLServerXADataSource 類別取得的 JDBC 連線可參與標準分散式交易處理環境,例如 Java Platform Enterprise Edition (Java EE) 應用程式伺服器。
在此文章中,XA 代表延伸架構。
警告
Microsoft JDBC Driver 4.2 (和更新版本) for SQL 包含新的現有功能逾時選項,用於已取消準備交易的自動回復。 如需詳細資訊,請參閱此主題稍後的針對已取消準備之交易的自動回復設定伺服器端逾時設定。
備註
分散式交易實作的類別如下:
類別 | 實作 | 描述 |
---|---|---|
com.microsoft.sqlserver.jdbc.SQLServerXADataSource | javax.sql.XADataSource | 分散式連接的 Class Factory。 |
com.microsoft.sqlserver.jdbc.SQLServerXAResource | javax.transaction.xa.XAResource | 交易管理員的資源配接器。 |
注意
XA 分散式交易連接會預設為「讀取認可」隔離等級。
使用 XA 交易時的指導方針和限制
下列額外指導方針適用於緊密繫結的交易:
當您將 XA 交易與 Microsoft Distributed Transaction Coordinator (MS DTC) 一起使用時,可能會注意到目前版本的 MS DTC 不支援緊密繫結的 XA 分支行為。 例如,MS DTC 在 XA 分支交易識別碼 (XID) 與 MS DTC 交易識別碼之間擁有一對一的對應,而且由鬆散繫結之 XA 分支所執行的工作會彼此隔離。
MS DTC 也支援緊密結合的 XA 分支,其中具有相同全域交易識別碼 (GTRID) 的多個 XA 分支均會對應到單一 MS DTC 交易識別碼。 此支援讓多個緊密結合的 XA 分支可以在資源管理員 (例如 SQL Server) 中查看彼此的變更。
SSTRANSTIGHTLYCPLD 旗標允許應用程式使用緊密結合的 XA 交易,這些交易的 XA 分支交易識別碼 (BQUAL) 各不相同,但具有相同的全域交易識別碼 (GTRID) 和格式識別碼 (FormatID)。 若要使用該功能,您必須在 XAResource.start 方法的 flags 參數上,設定 SSTRANSTIGHTLYCPLD:
xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
設定指示
如果要將 XA 資料來源與 Microsoft 分散式交易協調器 (MS DTC) 搭配使用,來處理分散式交易,則需要下列步驟。 高階步驟為:
- 確定 MS DTC 服務正在執行並自動啟動。
- 設定伺服器端元件。
- 設定伺服器端逾時 (選用)。
- 授與使用者的存取權。
執行 MS DTC 服務
應該在服務管理員中將 MS DTC 服務標示為自動,以確定啟動 SQL Server 服務時,便執行該服務。 若要啟用 MS DTC 以用於 XA 交易,您必須遵循下列步驟:
在 Windows Vista 及更新的版本上:
選取開始按鈕,在開始搜尋方塊中鍵入 dcomcnfg,然後按下 ENTER 開啟元件服務。 您也可以在開始搜尋方塊中鍵入
%windir%\system32\comexp.msc
,開啟元件服務。依序展開 [元件服務]、[電腦]、[我的電腦] 和 [分散式交易協調器]。
以滑鼠右鍵按一下 [本機 DTC] ,然後選取 [內容] 。
選取本機 DTC 內容對話方塊中的安全性索引標籤。
選取啟用 XA 交易核取方塊,然後按一下確定。 此動作會導致 MS DTC 服務隨即重新啟動。
再次選取確定以關閉內容對話方塊,然後關閉元件服務。
停止然後重新啟動 SQL Server,以確保其與 MS DTC 變更保持同步。 (此步驟對於 SQL Server 2019 和 SQL Server 2017 CU 16 和更新版本而言是選擇性的。)
設定 JDBC 分散式交易元件
設定伺服器端元件的步驟會根據您的目標伺服器版本而有所不同。 若要檢查伺服器版本,請對伺服器執行查詢 SELECT @@VERSION
並檢視輸出。 針對 SQL Server 2017 累積更新 (CU) 16 和更新版本,請遵循 SQL Server 2017 CU16 和更高版本的指示。 針對較舊的 SQL Server 版本,請遵循 SQL Server 2017 CU15 和較舊版本的指示。
SQL Server 2017 CU16 和更新版本
若要使用 JDBC 驅動程式啟用必要元件以執行 XA 分散式交易,請執行下列預存程序。
EXEC sp_sqljdbc_xa_install
若要停用元件,請執行下列預存程序。
EXEC sp_sqljdbc_xa_uninstall
略過前往設定已取消準備交易的自動回復之伺服器端逾時設定章節。
SQL Server 2017 CU15 和更低版本
注意
這僅適用於 SQL Server 2017 CU15 和更低版本。 sqljdbc_xa.dll 所提供的功能已包含在 SQL Server 2017 CU16 和更新版本中。
JDBC 分散式交易元件會包含在 JDBC 驅動程式安裝的 xa 目錄中。 這些元件包括 xa_install.sql 及 sqljdbc_xa.dll 檔案。 如果不同用戶端上使用不同版本的 JDBC 驅動程式,則建議在伺服器上使用最新的 sqljdbc_xa.dll。
您可以遵循下列步驟,來設定 JDBC 驅動程式分散式交易元件:
將新的 sqljdbc_xa.dll 從 JDBC 驅動程式安裝目錄複製至將參與分散式交易之每一部 SQL Server 電腦的 Binn 目錄。
注意
如果您搭配使用 XA 交易與 32 位元 SQL Server (僅適用於 SQL Server 2014 或更舊版本),則即使 SQL Server 安裝在 x64 處理器上,也請使用 x86 資料夾中的 sqljdbc_xa.dll 檔案。 如果您在 x64 處理器上搭配使用 XA 交易與 64 位元 SQL Server,則請使用 x64 資料夾中的 sqljdbc_xa.dll 檔案。
在每個將參與分散式交易的 SQL Server 執行個體上執行資料庫指令碼 xa_install.sql。 這個指令碼就會安裝 sqljdbc_xa.dll 所呼叫的擴充預存程序。 這些擴充預存程序會實作適用於 SQL Server 的 Microsoft JDBC 驅動程式的分散式交易和 XA 支援。 您需要以 SQL Server 執行個體管理員身分來執行此指令碼。
若要授與權限給特定使用者以使用 JDBC Driver 參與分散式交易,請將該使用者新增至 SqlJDBCXAUser 角色。
您一次只能針對每個 SQL Server 執行個體設定單一 sqljdbc_xa.dll 組件版本。 應用程式可能需要使用不同的 JDBC 驅動程式版本,透過使用 XA 連接來連接至相同的 SQL Server 執行個體。 在該情況下,您就必須在 SQL Server 執行個體上安裝隨附於最新 JDBC Driver 的 sqljdbc_xa.dll。
有三種方式可確認目前安裝在 SQL Server 執行個體上的 sqljdbc_xa.dll 版本:
開啟將參與分散式交易之 SQL Server 電腦的 LOG 目錄。 選取並開啟 SQL Server "ERRORLOG" 檔案。 在 "ERRORLOG" 檔案中搜尋 "Using 'SQLJDBC_XA.dll' version ..." 片語。
開啟將參與分散式交易之 SQL Server 電腦的 Binn 目錄。 選取 sqljdbc_xa.dll 組件。
- 在 Windows Vista 或更新的版本上:以滑鼠右鍵按一下 sqljdbc_xa.dll,然後選取 [內容]。 然後選取詳細資料索引標籤。檔案版本欄位會顯示目前安裝在 SQL Server 執行個體上的 sqljdbc_xa.dll 版本。
依照下一節的程式碼範例所示,設定記錄功能。 在輸出記錄檔案中搜尋 "Server XA DLL version:..." 片語。
升級 sqljdbc_xa.dll
注意
這僅適用於 SQL Server 2017 CU15 和更低版本。 sqljdbc_xa.dll 所提供的功能已包含在 SQL Server 2017 CU16 和更新版本中。
當您安裝新版 JDBC 驅動程式時,也應該使用新版的 sqljdbc_xa.dll 來升級伺服器上的 sqljdbc_xa.dll。
重要
您應利用維護時段,或沒有任何 MS DTC 交易正在進行時,升級 sqljdbc_xa.dll。
使用 Transact-SQL 命令來卸載 sqljdbc_xa.dll:
DBCC sqljdbc_xa (FREE)
將新的 sqljdbc_xa.dll 從 JDBC 驅動程式安裝目錄複製至將參與分散式交易之每一部 SQL Server 電腦的 Binn 目錄。
當系統呼叫 sqljdbc_xa.dll 中的擴充程序時,就會載入新的 DLL。 您不需要重新啟動 SQL Server,即可載入新的定義。
設定已取消準備交易的自動回復之伺服器端逾時設定
有兩個登錄設定 (DWORD 值) 用來控制分散式交易的逾時行為:
XADefaultTimeout
(以秒為單位):若使用者未指定任何逾時,此時所要使用的預設逾時值。 預設值是 0。XAMaxTimeout
(以秒為單位):使用者可以設定的逾時最大值。 預設值是 0。
這些是 SQL Server 執行個體的特定設定,而且應該建立在下列登錄機碼底下:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
注意
在 64 位元機器中執行的 32 位元 SQL Server (僅適用於 SQL Server 2014 或更舊版本),應在下列金鑰底下建立登錄設定:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
如果逾時到期,當此設定已啟動且已由 SQL Server 復原交易時,會設定每一筆交易的逾時值。 根據這些登錄設定,並根據使用者已透過 XAResource.setTransactionTimeout() 指定的設定,來決定此逾時。 這些逾時值解譯方式的幾個範例,如下所示:
XADefaultTimeout = 0
,XAMaxTimeout = 0
表示不使用預設的逾時,在用戶端上將不強制執行最大逾時。 在此情況下,只有當用戶端使用 XAResource.setTransactionTimeout 設定逾時,交易才會有逾時。
XADefaultTimeout = 60
,XAMaxTimeout = 0
表示如果用戶端未指定任何逾時,則所有交易都有 60 秒逾時。 如果用戶端指定了逾時,則會使用該逾時值。 不會強制執行逾時最大值。
XADefaultTimeout = 30
,XAMaxTimeout = 60
表示如果用戶端未指定任何逾時,則所有交易都有 30 秒逾時。 用戶端如有指定逾時,除非該值小於 60 秒 (最大值),否則將會使用用戶端的逾時值。
XADefaultTimeout = 0
,XAMaxTimeout = 30
表示如果用戶端未指定任何逾時,則所有交易都有 30 秒逾時 (最大值)。 用戶端如有指定逾時,除非該值小於 30 秒 (最大值),否則將會使用用戶端的逾時值。
設定使用者定義角色
若要授與權限給特定使用者以使用 JDBC Driver 參與分散式交易,請將該使用者新增至 SqlJDBCXAUser 角色。 例如,使用下列 Transact-SQL 程式碼,將名為 'shelly' (名為 'shelly' 的 SQL 標準登入使用者) 的使用者新增至 SqlJDBCXAUser 角色:
USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'
每一個資料庫都會定義 SQL 使用者自訂角色。 若要基於安全目的來建立您自己的角色,您必須在每一個資料庫中定義角色,然後在每一個資料庫中新增使用者。 SqlJDBCXAUser 角色一定是定義在 master 資料庫中,因為它是用來授與存取 SQL JDBC 擴充預存程序 (位於 master 資料庫) 的權限。 您必須先授與個別使用者存取 master 的權限,然後在您登入 master 資料庫時,再授與他們存取 SqlJDBCXAUser 角色的權限。
範例
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 = "<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);
}
}