4. 事务复制原理与配置实战

事务复制是 SQL Server 复制体系中最贴近“准实时数据同步”需求的一种方案。它的核心思想并不复杂:发布端把事务日志中已经提交、且命中发布项目的变更读出来,交给分发端暂存,再由分发代理按顺序投递到订阅端。整个过程围绕“日志读取—命令分发—订阅应用”三段链路展开,而初始化快照则负责把订阅端的起点数据补齐。理解这条链路,是完成单表事务复制部署的前提。

一、事务复制的工作流与三个代理

事务复制由三个代理协同完成,它们各自负责一段职责,缺一不可:

  • 快照代理(Snapshot Agent):在发布端生成表结构与初始数据文件(.sch 与 .bcp),写入分发库的快照文件夹,并记录同步起点 LSN。
  • 日志读取代理(Log Reader Agent):扫描发布库事务日志,把已提交且命中发布项目的事务转换为命令,写入分发库的 MSrepl_commands 与 MSrepl_transactions。
  • 分发代理(Distribution Agent):从分发库取出命令,按事务顺序在订阅端重放,保证订阅端最终与发布端一致。

这三者的关系可以用下面的信号链表示:发布库日志 → 日志读取代理 → 分发库命令表 → 分发代理 → 订阅库。快照代理则在链路启动前提供初始基线。

发布库 Publisher 事务日志 快照代理 生成 .sch / .bcp 记录初始 LSN 日志读取代理 扫描日志 → 命令 写入分发库 分发库 Distribution MSrepl_commands 订阅库 Subscriber 目标表 分发代理 按序重放命令 快照提供初始基线,日志读取与分发代理持续同步增量变更

二、日志读取与命令分发

日志读取代理并不是“读表”,而是读事务日志。它从上次记录的 LSN 位置开始,逐条解析日志记录,只保留已提交事务中命中发布项目的操作。对于 UPDATE,它会根据项目设置决定是否拆分为“删除+插入”或直接生成 UPDATE 命令;对于 INSERT、DELETE 则分别生成对应命令。这些命令连同事务边界一起写入分发库,形成待投递队列。

分发代理从分发库按事务顺序取出命令,在订阅端以相同顺序执行。这里有一个关键点:事务复制默认保证事务原子性,即一个事务内的多条命令要么全部应用,要么全部回滚,因此订阅端不会看到“半个事务”的中间状态。分发代理的投递延迟通常用“分发延迟”和“未分发命令数”两个指标观察,前者反映时间差,后者反映积压量。

三、初始化快照应用

快照是订阅端的起点。如果订阅端表为空,必须先应用快照,否则后续增量命令会因缺少基线数据而失败。快照应用有两种方式:一是由分发代理自动应用(默认),二是手动通过 bcp 或 SSMS 导入。对于单表复制,快照通常很小,自动应用即可。

需要特别注意的是快照与日志读取的衔接:快照代理在生成快照时会记录一个 LSN,日志读取代理只读取该 LSN 之后的变更。这样订阅端先拿到“某时刻的完整数据”,再接收“该时刻之后的增量”,两者拼接后才是完整一致的数据。如果快照应用失败或中断,必须重新初始化,不能只补增量。

四、可更新订阅选项

标准事务复制是单向的:发布端写,订阅端读。如果业务需要在订阅端也能写入,就需要启用可更新订阅。SQL Server 提供两种模式:

  • 立即更新(Immediate Updating):订阅端写入时,通过 MSDTC 分布式事务直接更新发布端,发布端再复制回订阅端。一致性最强,但对网络和事务协调器依赖高。
  • 排队更新(Queued Updating):订阅端写入先进入队列,异步应用到发布端。可用性更好,但存在冲突可能,需要配置冲突检测与解决策略。

对于单表事务复制部署,如果只是做报表库或只读副本,保持默认的只读订阅即可,不必开启可更新订阅,以免引入额外复杂度。

五、单表事务复制部署示例

下面以发布库 SalesDB 中的 dbo.Orders 表为例,演示单表事务复制的完整配置。示例参数为教学假设,实际部署时请按环境调整。

-- 1. 在发布端配置分发(若尚未配置)
USE master;
EXEC sp_adddistributor @distributor = N'SQLPUB01', @password = N'P@ssw0rd';
EXEC sp_adddistributiondb @database = N'distribution', @data_folder = N'D:\ReplData';
EXEC sp_adddistpublisher @publisher = N'SQLPUB01', @distribution_db = N'distribution',
     @working_directory = N'\\SQLPUB01\ReplData';

-- 2. 创建发布(事务复制)
USE SalesDB;
EXEC sp_replicationdboption @dbname = N'SalesDB', @optname = N'publish', @value = N'true';
EXEC sp_addpublication @publication = N'Pub_Orders', @status = N'active',
     @repl_freq = N'continuous', @sync_method = N'concurrent';

-- 3. 添加单表项目
EXEC sp_addarticle @publication = N'Pub_Orders', @article = N'Orders',
     @source_object = N'Orders', @type = N'logbased',
     @schema_option = 0x000000000803509F;

-- 4. 创建订阅(推送订阅)
EXEC sp_addsubscription @publication = N'Pub_Orders', @subscriber = N'SQLSUB01',
     @destination_db = N'SalesDB_Replica', @subscription_type = N'push',
     @sync_type = N'automatic', @update_mode = N'read only';

-- 5. 启动快照代理与分发代理
EXEC sp_startpublication_snapshot @publication = N'Pub_Orders';

执行完成后,可以在分发库中查询 MSrepl_commands 观察命令是否持续写入,在订阅端查询 dbo.Orders 的行数与发布端对比。判断部署是否成功的依据是:快照应用后两端行数一致;在发布端插入一行后,订阅端在数秒内出现同一行;分发代理历史中无连续错误。

单表事务复制部署的关键检查点:① 发布库必须处于完整恢复模式;② SQL Server 代理服务必须运行;③ 快照文件夹对分发代理账户可读写;④ 订阅端表结构由快照自动创建,不要手工建表后再应用快照。
如果日志读取代理报“无法读取日志”或分发代理报“主键冲突”,通常说明快照未成功应用或订阅端已有数据。此时应停止分发代理,重新初始化订阅,而不是反复重启代理。

完成以上步骤后,一条从发布端事务日志到订阅端目标表的准实时同步链路就建立起来了。后续的监控、延迟排查与冲突处理,都围绕这条链路展开。