为 Always On 可用性组配置分布式事务

适用于: SQL Server

SQL Server 2017 (14.x) 及更高版本支持所有分布式事务,包括可用性组中的数据库。 本文介绍如何为分布式事务配置可用性组

为确保分布式事务的一致性,必须配置可用性组,使其将数据库注册为分布式事务资源管理器。

注意

SQL Server 2016 (13.x) 服务包 2 及更高版本完全支持可用性组中的分布式事务。 SQL Server 2016 (13.x) 服务包 1 及更低版本不支持涉及可用性组中数据库的跨数据库分布式事务(即,使用同一 SQL Server 实例上数据库的事务)。 SQL Server 2017 (14.x) 没有此限制。

SQL Server 2016 (13.x) 中的配置步骤与 SQL Server 2017 (14.x) 相同。

在分布式事务中,客户端应用程序和 Microsoft 分布式事务处理协调器(MSDTC 或 DTC)共同配合来确保多个数据源之间的事务一致性。 DTC 是在基于 Windows Server 的受支持操作系统上提供的服务。 DTC 充当分布式事务的“事务处理协调器”。 SQL Server 实例通常充当“资源管理器”。 当数据库位于可用性组中时,每个数据库需为其自身的资源管理器。

SQL Server 不会阻止可用性组中数据库的分布式事务 - 即使该可用性并非为分布式事务配置时亦不例外。 但是,如果可用性组并非为分布式事务配置的,在某些情况下故障转移可能不会成功。 特别是新的主要副本 SQL Server 实例可能无法从 DTC 获取事务结果。 若要启用 SQL Server 实例,以在故障转移后从 DTC 获取未决事务的结果,请为分布式事务配置可用性组。

除非数据库也是故障转移群集的成员,否则可用性组处理不会涉及 DTC。 在可用性组中,副本之间的一致性是通过可用性组逻辑维护的:在辅助副本确认其已在持久存储中保留日志记录之前,主副本不会完成提交,也不会向调用方确认提交。 只有到那时,主副本才会声明该事务完成。 在异步模式下,我们不会等待辅助副本确认,此模式下明显有丢失少量数据的可能性。

先决条件

将可用性组配置为支持分布式事务前,必须满足以下先决条件:

  • 参与分布式事务的 SQL Server 的所有实例必须为 SQL Server 2016 (13.x) 或更高版本。

  • 可用性组必须在 Windows Server 2012 R2 或更高版本上运行。 对于 Windows Server 2012 R2,须安装 KB3090973 中的更新。

为分布式事务创建可用性组

配置可用性组以支持分布式事务。 设置可用性组以允许所有数据库都注册为资源管理器。 本文介绍如何配置可用性组,以便所有数据库都可成为 DTC 中的资源管理器。

可在 SQL Server 2016 (13.x) 或更高版本上为分布式事务创建可用性组。 若要为分布式事务创建可用性组,请在可用性组定义中包含 DTC_SUPPORT = PER_DB。 以下脚本将为分布式事务创建可用性组。

CREATE AVAILABILITY
GROUP MyAG
WITH (DTC_SUPPORT = PER_DB)
FOR DATABASE DB1,
    DB2 REPLICA
ON 'Server1' WITH (
   ENDPOINT_URL = 'TCP://SERVER1.corp.com:5022',
   AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
   FAILOVER_MODE = AUTOMATIC
),
'Server2' WITH (
   ENDPOINT_URL = 'TCP://SERVER2.corp.com:5022',
   AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
   FAILOVER_MODE = AUTOMATIC
);

注意

上面的脚本创建的是简单的可用性组示例,并不针对任何特定生产环境。

更改分布式事务的可用性组

可在 SQL Server 2017 (14.x) 或更高版本上更改分布式事务的可用性组。 若要更改分布式事务的可用性组,请在 ALTER AVAILABILITY GROUP 脚本中包含 DTC_SUPPORT = PER_DB。 示例脚本将更改可用性组以支持分布式事务。

ALTER AVAILABILITY GROUP MyaAG
SET (DTC_SUPPORT = PER_DB);

注意

在 SQL Server 2016 (13.x) 服务包 2 及更高版本中,可更改分布式事务的可用性组。 对于 Service Pack 2 之前的 SQL Server 2016 (13.x) 版本,则需要进行删除,然后使用 DTC_SUPPORT = PER_DB 设置重新创建可用性组。

若要禁用分布式事务,请使用以下 Transact-SQL 命令:

ALTER AVAILABILITY GROUP MyaAG
SET (DTC_SUPPORT = NONE);

分布式事务 - 技术概念

分布式事务可跨两个或多个数据库。 作为事务管理器,DTC 可协调 SQL Server 实例之间和其他数据源之间的事务。 SQL Server 数据库引擎的每个实例都可以充当资源管理器。 如果使用 DTC_SUPPORT = PER_DB 配置可用性组,数据库也可以充当资源管理器。 有关详细信息,请参阅 MSDTC 文档。

在数据库引擎的单个实例中具有两个或多个数据库的事务实际上是分布式事务。 该实例对分布式事务进行内部管理;对于用户而言,其操作就像本地事务一样。 当数据库位于使用 DTC_SUPPORT = PER_DB 配置的可用性组中(甚至是在 SQL Server 的单个实例内)时,SQL Server 2017 (14.x) 会将所有跨数据库的事务提升到 DTC。

对于应用程序而言,管理分布式事务很像管理本地事务。 当事务结束时,应用程序会请求提交或回滚事务。 事务管理器必须以不同的方式管理分布式提交,以尽量减少因网络故障导致某些资源管理器成功提交,但其他资源管理器将事务回滚的风险。 通过分两个阶段(准备阶段和提交阶段)管理提交进程可避免这种情况,这称为两阶段提交。

  • 准备阶段

    当事务管理器收到提交请求时,它会向该事务涉及的所有资源管理器发送准备命令。 然后,每个资源管理器将尽力使该事务持久,并且所有保存该事务日志映像的缓冲区将被刷新到磁盘中。 当每个资源管理器完成准备阶段时,它会向事务管理器返回准备阶段成功或准备失败的消息。

  • 提交阶段

    如果事务管理器从所有资源管理器收到准备成功的消息,它将向每个资源管理器发送一个提交命令。 然后,资源管理器就可以完成提交。 如果所有资源管理器都报告提交成功,那么事务管理器就会向应用程序发送一个成功通知。 如果任一资源管理器报告准备失败,那么事务管理器将向每个资源管理器发送一个回滚命令,并向应用程序表明提交失败。

详细步骤

以下列表说明应用程序如何与 DTC 配合完成分布式事务。

  1. SQL Server 实例在 DTC 事务中登记。 当事务中有多个资源管理器或客户端请求将事务提升到 DTC 事务时,可能会出现此情况。
  2. 客户端在 SQL Server 实例中的 DTC 事务下执行某些工作。
  3. 客户端发起 DTC 事务提交或中止。
    • 如果客户端发起中止,事务将立即中止。
    • 如果客户端发起提交,DTC 将通知事务中的所有资源管理器准备事务,从而启动两阶段提交协议。
  4. 当所有资源均成功收到准备阶段的通知后,DTC 将通知所有资源管理器提交事务。 如果有任何原因导致未成功收到通知,DTC 将中止事务。

为分布式事务配置可用性组的影响

参与分布式事务的每个实体都可称为资源管理器。 资源管理器的示例包括:

  • 一个 SQL Server 实例。
  • 为分布式事务配置的可用性组中的数据库。
  • DTC 服务 - 也可以充当事务管理器。
  • 其他数据源。

若要参与分布式事务,SQL Server 的实例需要向 DTC 登记。 通常情况下,SQL Server 的实例在本地服务器上向 DTC 登记。 SQL Server 的每个实例都会创建一个具有唯一资源管理器标识符 (RMID) 的资源管理器并将其注册到 DTC。 在默认配置中,SQL Server 实例上的所有数据库都使用相同的 RMID。

当数据库位于可用性组中时,数据库的读写副本或主要副本可能会移动到其他 SQL Server 实例上。 若要在此移动过程中支持分布式事务,每个数据库都应充当单独的资源管理器,并且必须具有唯一的 RMID。 当可用性组具有 DTC_SUPPORT = PER_DB 时,SQL Server 将为每个数据库创建资源管理器,并且使用唯一的 RMID 将它们向 DTC 注册。 在此配置中,数据库充当于 DTC 事务的资源管理器。

重要

每个分布式事务的 DTC 不超过 32 个登记。 由于可用性组中的每个数据库会单独使用 DTC 进行登记,因此如果事务涉及到 32 个以上的数据库,则在 SQL Server 尝试登记第 33 个数据库时,可能会遇到以下错误:

Enlist operation failed: 0x8004d101(XACT_E_TOOMANY_ENLISTMENTS). SQL Server couldn't register with Microsoft Distributed Transaction Coordinator (MSDTC) as a resource manager for this transaction. The transaction might have been stopped by the client or the resource manager.

有关 SQL Server 中的分布式事务的详细信息,请参阅分布式事务

管理未解决的事务

故障转移后,可能无法恢复 RMID 更改期间存在的活动事务的结果。 这是因为用于登记的 RMID SQL Server 和用于恢复的 RMID SQL Server 是不同的。 以下情况可能导致 RMID 更改:

  • 更改可用性组的 DTC_SUPPORT
  • 在可用性组中添加或删除数据库。
  • 删除可用性组。

在上述情况下,如果主要副本故障转移到 SQL Server 的新实例,该实例将尝试联系 DTC 以确定事务结果。 DTC 无法返回结果,因为数据库用于获取恢复期间未决事务的结果的 RMID 之前未进行登记。 因此,数据库将进入 SUSPECT 状态。

新 SQL Server 错误日志中的一个条目将如下例所示:

Microsoft Distributed Transaction Coordinator (MSDTC)
failed to reenlist citing that the database RMID does
not match the RMID [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
associated with the transaction.  Please manually resolve
the transaction.

SQL Server detected a DTC/KTM in-doubt transaction with UOW
{yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy}.Please resolve it
following the guideline for Troubleshooting DTC Transactions.

前面的示例说明,DTC 无法通过故障转移后创建的事务中的新主要副本重新登记数据库。 SQL Server 实例不能确定分布式事务的结果,因此它将数据库标记为可疑。 事务将被标记为工作单位 (UOW),并由 GUID 引用。 为了恢复数据库,请手动提交事务或回滚事务。

警告

手动提交或回滚事务时,应用程序可能受到影响。 请验证提交或回滚操作是否符合应用程序要求。

仅运行以下脚本之一:

  • 若要提交事务,请更新并运行以下脚本 - 使用前一条错误消息中的未决事务 UOW 替换 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy,然后运行:

    KILL 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' WITH COMMIT;
    
  • 若要回滚事务,请更新并运行以下脚本 - 使用前一条错误消息中的未决事务 UOW 替换 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy,然后运行:

    KILL 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' WITH ROLLBACK;
    

提交或回滚事务后,可使用 ALTER DATABASE 将数据库设置为联机。 更新并运行以下脚本 - 为可疑数据库设置名称:

ALTER DATABASE [DB1] SET ONLINE;

有关解决未决事务的详细信息,请参阅手动解决事务