必须显式使用begin distributed transaction跨服务器,因sql server在链接服务器操作中自动升级分布式事务依赖msdtc就绪;任一配置缺失(dtc服务、端口、安全设置、rpc out)或代码违规(未设xact_abort、动态sql、环回调用)均导致事务降级为本地事务,引发数据不一致。

不能直接用 BEGIN TRANSACTION 跨服务器,必须显式声明 BEGIN DISTRIBUTED TRANSACTION,否则事务会静默降级为本地事务,远程操作不参与回滚。
为什么普通事务在跨服务器时失效
SQL Server 检测到语句涉及链接服务器(如 SELECT * FROM [srv_link].db.schema.table)时,会尝试自动升级为分布式事务——但前提是 MSDTC 已就绪且配置正确。一旦任一环节缺失(比如 DTC 服务未启动、RPC 端口被拦、XACT_ABORT 关闭),SQL Server 就放弃升级,只对本地部分执行 BEGIN TRAN,远程操作变成自动提交(autocommit),导致数据不一致。
常见错误现象包括:
- 执行
ROLLBACK TRANSACTION后,本地表回滚了,远程表仍保留插入/更新 - 报错
Msg 7391, Level 16, State 2:“无法执行该操作,因为该事务已作为分布式事务启动”(说明已触发升级但失败) - 报错
Msg 7395, Level 16, State 2:“无法执行该操作,因为该事务尚未作为分布式事务启动”(说明根本没升级)
MSDTC 必须完成的 4 项硬性配置
缺一不可,顺序无关,但任意一项失败都会让 BEGIN DISTRIBUTED TRANSACTION 报错或静默失败:
- 双方服务器上都运行
msdtc服务(Windows 服务名:Distributed Transaction Coordinator) - 双方防火墙放行 TCP 135 端口(RPC 终结点映射器),并添加
%windir%\system32\msdtc.exe到例外列表 - 组件服务 → 我的电脑 → 属性 → MSDTC → 安全配置中勾选:
网络DTC访问、允许远程客户端、允许远程管理、允许入站、允许出站;认证级别选不要求进行验证(开发/内网环境)或请求验证(生产) - 链接服务器选项中启用
rpc out:EXEC sp_serveroption 'srv_link', 'rpc out', 'true'(否则无法调用远程存储过程)
存储过程中调用远程 SP 的写法要点
即使配置全部正确,代码写法不对也会绕过分布式事务。关键约束如下:
- 必须在
BEGIN DISTRIBUTED TRANSACTION之后,才执行任何跨服务器语句(含EXEC [srv_link]...sp_name或四部分命名查询) - 事务块内所有语句前必须加
SET XACT_ABORT ON(不是可选,是强制要求) - 禁止“环回(loopback)”:远程服务器上的视图/存储过程不能反向查询发起事务的本机服务器,否则事务直接失败
- 链接服务器名不能用变量拼接(如
EXEC('EXEC '+@srv+'.db.dbo.sp')),动态 SQL 会脱离当前事务上下文
示例片段:
SET XACT_ABORT ON
BEGIN DISTRIBUTED TRANSACTION
INSERT INTO local_table VALUES ('a')
EXEC [srv_link].RemoteDB.dbo.RemoteProc @p = 1
UPDATE [srv_link].RemoteDB.dbo.RemoteTable SET x = 1 WHERE id = 1
COMMIT TRANSACTION
Linux 上 SQL Server 的特殊处理
Linux 容器或 RHEL/CentOS 部署的 SQL Server 不走 Windows DTC,而是通过 mssql-conf 配置原生 MSDTC 支持:
- 必须显式设置 RPC 端口:
mssql-conf set network.rpcport 135 - 必须固定 MSDTC 服务端口:
mssql-conf set distributedtransaction.servertcpport 51000(避免随机端口导致防火墙策略失效) - 需用
iptables将 135 端口流量 NAT 到 SQL Server 进程(因 Linux 非 root 进程无法绑定 - 连接字符串或链接服务器定义中,
@provider应为MSOLEDBSQL(新版驱动),而非已弃用的SQLOLEDB
最易忽略的是端口映射和权限配置——哪怕 Windows 侧一切正常,Linux 侧只要 rpcport 或 servertcpport 未固化,重启后就会失联。










