SQL Server发布与订阅:事务复制原理与生产级配置指南

SQL Server发布与订阅:事务复制原理与生产级配置指南 1. 什么是 SQL Server 的发布与订阅它到底解决什么问题SQL Server 的发布与订阅不是新闻网站的“订阅 newsletter”也不是视频平台的“开通会员”而是一套内建于 SQL Server 数据库引擎中的、成熟稳定的数据分发与同步机制。它的核心目标非常明确让一份数据在多个物理位置、多个独立数据库实例之间保持准实时、可控制、可追溯的一致性。我第一次在客户现场部署这套机制时是在一家连锁零售企业的总部与23家门店之间——总部每天生成销售汇总报表但门店经理需要本地访问自己店的明细数据同时又不能直接连总部数据库网络延迟高、安全策略严、并发压力大。这时候“发布与订阅”就成了唯一不依赖第三方中间件、不修改应用代码、且能被 DBA 完全掌控的解决方案。它的本质是“一对多”的数据流管道一个数据库发布者将特定表或视图的数据变更INSERT/UPDATE/DELETE打包成事务或快照通过分发器Distributor这个中间协调角色推送到一个或多个订阅者Subscriber数据库中。整个过程对上层应用完全透明——订阅端的数据库就像一个“只读副本”应用照常查询只是背后的数据源已悄然切换。这和简单的备份还原有本质区别备份是静态快照而发布订阅是持续流动的“数据溪流”支持增量更新延迟通常控制在秒级。它也不同于 Always On 可用性组——后者是高可用容灾方案主备库角色严格绑定而发布订阅中订阅库可以是只读的报表库、开发测试库甚至可以是不同版本的 SQL Server 实例比如 SQL Server 2016 订阅 SQL Server 2019 的发布灵活性极高。为什么现在还有人用它因为很多场景下它依然是最“省心”的选择。比如你有一个核心业务库需要把客户主数据同步给 CRM 系统、把订单数据同步给 BI 报表平台、把库存数据同步给物流调度系统。如果每个系统都直连核心库不仅带来巨大的连接数压力更埋下了严重的安全与性能隐患。而通过发布订阅你可以为每个下游系统创建独立的订阅设置不同的同步频率每分钟、每小时、甚至按需触发还能精细控制哪些列、哪些行被同步通过筛选谓词真正做到“按需分发”。它不依赖网络专线也不要求所有节点在同一域内只要网络连通、端口开放、权限配置正确就能跑起来。我见过最复杂的案例是一个跨国制造企业用它把德国总部的 BOM 表同步到中国、越南、墨西哥三地的工厂数据库跨时区、跨防火墙稳定运行了五年零故障。所以当你看到“sqlserver 发布 订阅”这个关键词时它背后代表的不是一个功能按钮而是一整套经过二十年生产环境锤炼的数据治理基础设施。2. 发布与订阅的三种模式选错模式后面全是坑SQL Server 的复制Replication技术包含快照复制、事务复制和合并复制三种逻辑类型而“发布与订阅”这个说法通常特指其中最常用、最可靠的事务复制Transactional Replication。但要真正用好它必须先搞懂这三种模式的本质差异否则在设计阶段就埋下了无法挽回的隐患。我曾经帮一个金融客户重构数据同步方案他们最初选了合并复制结果上线后发现每日凌晨批量对账时大量冲突导致同步中断排查了三天才发现是模式选错了——这种教训值得花时间讲透。2.1 快照复制Snapshot Replication适合“静态”数据的“一次性搬运工”快照复制的工作原理极其简单它定期比如每天凌晨2点在发布服务器上对指定的表或视图执行一次完整的 SELECT *将所有数据导出为一组 BCP 文件然后通过网络传输到订阅服务器再用 BULK INSERT 全量覆盖目标表。整个过程不关心数据是否发生变化只管“搬”。它的优势在于实现简单、无锁表风险因为是读取快照、对网络带宽要求低只传变化后的全量数据。但它最大的硬伤是无法处理增量更新。如果你的表每天只有几条记录变化却要全量搬运百万行效率极低如果表很大比如10GB一次快照可能耗时数小时期间订阅库数据完全停滞。提示快照复制最适合那些极少更新、但需要定期刷新的参考数据比如国家地区代码表、产品分类字典表、员工组织架构树变动频率低于一周一次。我在做某政务系统时就把“行政区划代码表”用快照复制每周日自动同步一次既保证了数据新鲜度又避免了复杂事务跟踪的开销。2.2 事务复制Transactional Replication生产环境的“主力引擎”这才是我们常说的“发布与订阅”的主力军。它的核心是利用 SQL Server 的事务日志Transaction Log作为数据变更的唯一源头。每当发布库中发生 INSERT/UPDATE/DELETE 操作SQL Server 会将这些操作记录写入日志。分发代理Log Reader Agent会持续扫描这个日志识别出属于发布对象的事务并将其序列化为“复制命令”存入分发数据库Distribution Database的系统表中。然后分发代理Distribution Agent再从分发库中读取这些命令按顺序应用到订阅库。整个过程是严格保序、强一致性的——订阅库看到的数据变更顺序和发布库完全一致。这意味着如果你在发布库中先更新客户地址再插入一笔新订单订阅库也必然以相同顺序执行这两个操作不会出现“订单指向了一个还没更新的旧地址”的逻辑错误。注意事务复制要求发布库必须启用“完整恢复模式”Full Recovery Model因为这是事务日志被完整保留的前提。如果误设为“简单恢复模式”日志会被频繁截断Log Reader Agent 就找不到历史变更同步必然失败。这是我见过最多的新手错误务必在创建发布前检查SELECT recovery_model_desc FROM sys.databases WHERE name YourDB。2.3 合并复制Merge Replication支持“双向编辑”的“协作型同步”合并复制的设计初衷是解决“离线办公”场景比如销售代表带着笔记本电脑出差本地数据库订阅端可以随时增删改数据等回到办公室联网后再将本地变更与中心服务器发布端进行“合并”。它通过在每张表中添加两个隐藏的全局唯一标识符GUID列rowguid和msrepl_tran_version来追踪每一行的版本和来源。同步时它会比较两端数据的版本号自动解决冲突如“最后写入获胜”或自定义业务规则。但正因如此它的开销巨大每张表都要加两列、索引要重建、同步过程要进行复杂的行级比对。而且一旦发生冲突处理逻辑非常脆弱极易导致数据不一致。警告除非你的业务明确要求“两端均可写”否则绝对不要选择合并复制。我曾接手一个电商项目开发团队听说“合并复制支持双向”就把它用在了订单库和库存库之间结果促销大促时库存扣减和订单创建在两端并发发生系统自动生成了数千条冲突记录人工核对花了整整两天。后来我们彻底重构用事务复制应用层消息队列才解决了问题。3. 从零开始搭建一套事务复制分发器、发布者、订阅者的实操配置详解搭建一套可用的事务复制绝不是点几下 SSMS 图形界面就能搞定的事。它涉及三个核心角色的部署与配置分发器Distributor、发布者Publisher和订阅者Subscriber。这三个角色可以部署在同一台服务器上适用于测试或小型环境但生产环境强烈建议物理分离——分发器最好独占一台中等配置的服务器因为它既是“数据中转站”又是“命令调度中心”资源消耗不容小觑。下面我将以一个典型生产环境为例发布者SQL01分发器SQLDIST订阅者SQLREP手把手带你走完全部流程每一步都附带关键参数说明和避坑提示。3.1 第一步在分发器上配置分发数据库Distribution Database分发数据库是整个复制系统的“心脏”所有待分发的事务命令都暂存在这里。它必须是一个独立的、专门为此目的创建的数据库不能复用现有业务库。在 SQLDIST 服务器上打开 SSMS连接后执行以下 T-SQL-- 创建分发数据库路径请根据实际磁盘空间调整 USE master; EXEC sp_adddistributiondb database distribution, -- 分发数据库名称固定为distribution或自定义 data_folder ND:\MSSQL\Data, -- 数据文件路径 log_folder ND:\MSSQL\Log, -- 日志文件路径 log_file_size 2, -- 日志文件初始大小MB history_retention 48, -- 历史记录保留小时数默认72建议设为48 security_mode 1; -- 1Windows认证0SQL Server认证生产环境必须用1 -- 配置分发器本身即SQLDIST服务器作为自己的分发器 EXEC sp_adddistributor distributor NSQLDIST, password NYourStrongPassword123!; -- 仅当security_mode0时需要否则忽略关键细节解析history_retention参数决定了复制监控历史的保存时长。默认72小时但对于一个高并发的系统日志增长很快如果磁盘空间不足历史记录会被自动清理导致你无法回溯三天前的同步失败原因。我习惯将其设为48并配合一个每日清理脚本确保磁盘不被撑爆。另外security_mode 1是强制要求意味着所有代理服务Log Reader, Distribution都必须以 Windows 域账户身份运行该账户需要对发布库、分发库、订阅库拥有 db_owner 权限。切记不要用 sa 或本地管理员账户这是安全审计的红线。3.2 第二步在发布者上启用发布Enable Publisher发布者是数据的源头。在 SQL01 上你需要告诉 SQL Server“这个数据库里的某些表我要对外发布”。执行以下命令-- 在发布数据库例如SalesDB上启用发布 USE SalesDB; EXEC sp_replicationdboption dbname NSalesDB, optname Npublish, value Ntrue; -- 创建发布Publication命名为SalesOrderPub EXEC sp_addpublication publication NSalesOrderPub, description N发布销售订单主表及明细表, sync_method Nnative, -- 原生BCP方式最快 retention 0, -- 保留期天0无限期但受分发库清理策略影响 allow_push Ntrue, -- 允许推送订阅Push Subscription allow_pull Ntrue, -- 允许拉取订阅Pull Subscription allow_anonymous Nfalse, -- 禁止匿名订阅安全第一 enabled_for_internet Nfalse, -- 不暴露到公网 snapshot_in_defaultfolder Ntrue, -- 快照存放在默认共享文件夹 compress_snapshot Ntrue, -- 压缩快照文件节省带宽 ftp_port 21, ftp_login Nanonymous, ftp_password Nanonymous, allow_subscription_copy Nfalse, add_to_active_directory Nfalse, repl_freq Ncontinuous, -- 连续模式Log Reader实时扫描 status Nactive, independent_agent Ntrue, -- 每个发布使用独立代理互不影响 immediate_sync Nfalse, -- 关键设为false避免首次同步时锁表 allow_sync_tran Nfalse, autogen_scripts Nfalse, allow_queued_tran Nfalse, allow_dts Nfalse, replicate_ddl 0, -- 不复制DDL变更如ALTER TABLE避免意外 allow_initialize_from_backup Nfalse;实操心得immediate_sync Nfalse是一个极易被忽略但至关重要的参数。如果设为 trueSSMS 在创建发布时会立即生成一个全量快照并在此期间对所有发布表加 Schema Stability 锁导致业务写入阻塞。在生产库上这可能意味着几分钟的业务停顿。设为 false 后快照需要手动初始化但你可以选择在业务低峰期执行完全规避风险。另外replicate_ddl 0强烈建议保持关闭。我见过太多次开发人员在发布库上执行了一个ALTER TABLE ADD COLUMN结果这个 DDL 命令被自动复制到所有订阅库而某个订阅库的表上有触发器或约束导致同步失败整个链路瘫痪。DDL 变更必须由 DBA 手动在所有节点上统一执行。3.3 第三步向发布中添加文章Articles并配置筛选“文章”Article就是你要发布的具体对象通常是表、视图或存储过程。在 SalesDB 中假设我们要发布Orders和OrderDetails两张表并且只同步状态为“已完成”的订单Status Completed以减少订阅库的数据量。执行-- 添加Orders表作为文章 EXEC sp_addarticle publication NSalesOrderPub, article NOrders, source_object NOrders, type Nlogbased, -- 基于日志的事务复制 description N销售订单主表, creation_script N, -- 空字符串表示不生成创建脚本 pre_creation_cmd Ndrop, -- 同步前先删除目标表仅首次 schema_option 0x000000000803509F, -- 二进制位掩码控制复制哪些属性 identityrangemanagementoption Nmanual, -- 手动管理标识列范围 destination_table NOrders, destination_owner Ndbo, status 24, -- 24已启用 vertical_partition Ntrue, -- 允许垂直分区选择列 ins_cmd NCALL [sp_MSins_dboOrders], -- 插入命令模板 del_cmd NCALL [sp_MSdel_dboOrders], -- 删除命令模板 upd_cmd NSCALL [sp_MSupd_dboOrders]; -- 更新命令模板 -- 为Orders表添加行筛选只同步Completed订单 EXEC sp_articlefilter publication NSalesOrderPub, article NOrders, filter_name NFilter_CompletedOrders, filter_clause N[Status] Completed; -- 添加OrderDetails表并建立与Orders的关联确保外键完整性 EXEC sp_addarticle publication NSalesOrderPub, article NOrderDetails, source_object NOrderDetails, type Nlogbased, description N销售订单明细表, creation_script N, pre_creation_cmd Ndrop, schema_option 0x000000000803509F, identityrangemanagementoption Nmanual, destination_table NOrderDetails, destination_owner Ndbo, status 24, vertical_partition Ntrue, ins_cmd NCALL [sp_MSins_dboOrderDetails], del_cmd NCALL [sp_MSdel_dboOrderDetails], upd_cmd NSCALL [sp_MSupd_dboOrderDetails]; -- 为OrderDetails添加关联筛选只同步父订单为Completed的明细 EXEC sp_articlefilter publication NSalesOrderPub, article NOrderDetails, filter_name NFilter_CompletedOrderDetails, filter_clause NEXISTS (SELECT * FROM [dbo].[Orders] AS o WHERE o.[OrderID] [OrderDetails].[OrderID] AND o.[Status] Completed);核心原理schema_option是一个 64 位的二进制掩码每一位代表一个复制选项。0x000000000803509F这个值是经过精心计算的它表示复制主键、唯一索引、检查约束、默认值、标识列但不复制标识种子、全文索引、XML 索引等但不复制触发器、用户定义函数、CLR 类型。这样既能保证数据结构完整又避免了因订阅库缺少依赖对象而导致的同步失败。你可以用 SSMS 的图形界面生成这个值但务必理解其含义而不是盲目复制。3.4 第四步在订阅者上创建订阅Subscription现在数据源和分发通道都准备好了最后一步是让 SQLREP “认领”这份数据。这里有两种模式推送订阅Push Subscription和拉取订阅Pull Subscription。推送订阅由分发器主动将数据推送到订阅者管理集中拉取订阅则由订阅者主动向分发器请求数据管理分散。生产环境推荐推送订阅因为 DBA 可以在分发器上统一监控和调优所有订阅。-- 在SQLREP上执行以下命令创建推送订阅 USE SalesDB; -- 注意这里仍需在发布库上下文中执行 EXEC sp_addsubscription publication NSalesOrderPub, subscriber NSQLREP, destination_db NSalesRepDB, -- 订阅数据库名 subscription_type Npush, sync_type Nautomatic, -- 自动初始化从快照同步 article Nall, -- 订阅所有文章 update_mode Nread only, -- 订阅库为只读防止误写 subscriber_type 0; -- 0SQL Server订阅者 -- 为推送订阅创建分发代理Distribution Agent EXEC sp_addpushsubscription_agent publication NSalesOrderPub, subscriber NSQLREP, subscriber_db NSalesRepDB, job_login NULL, -- 使用Windows域账户此处为空 job_password NULL, subscriber_security_mode 1, -- Windows认证 frequency_type 64, -- 64连续运行 frequency_interval 1, frequency_relative_interval 1, frequency_recurrence_factor 0, frequency_subday 4, -- 每分钟检查一次 frequency_subday_interval 1, active_start_time_of_day 0, active_end_time_of_day 235959, active_start_date 0, active_end_date 0, description N将SalesOrderPub推送到SQLREP;关键验证订阅创建后不要急于认为万事大吉。立刻打开 SSMS 的“复制监视器”Replication Monitor连接到分发器 SQLDIST找到你的发布SalesOrderPub查看“订阅”选项卡。正常状态下“状态”应为“正在运行”“延迟”应为“0 秒”或“几秒”。如果显示“未启动”或“失败”双击进去看详细错误日志——90% 的问题都源于权限不足Windows 域账户没有对订阅库的 db_owner 权限或网络不通SQLREP 无法访问 SQLDIST 的分发数据库端口。4. 复制代理的生命周期与性能调优让数据流稳如磐石发布与订阅的后台是由一系列 Windows 服务代理Agents驱动的。它们不是“一劳永逸”的守护进程而是有明确生命周期、需要持续监控和调优的“数据快递员”。理解它们的工作原理和常见瓶颈是保障复制稳定性的关键。我管理过一个日均处理 500 万笔交易的金融系统其复制链路曾因代理配置不当在季度结账日峰值时出现 15 分钟延迟差点导致监管报表延误。那次事故后我总结了一套完整的代理调优手册。4.1 三大核心代理及其职责日志读取器代理Log Reader Agent驻留在发布服务器SQL01上任务是持续扫描发布数据库的事务日志LDF 文件提取属于发布对象的事务并将它们写入分发数据库distribution的MSrepl_commands和MSrepl_transactions系统表中。它是整个数据流的“源头泵”其性能直接决定了变更能否被及时捕获。分发代理Distribution Agent驻留在分发器SQLDIST上对于推送订阅或驻留在订阅者SQLREP上对于拉取订阅。它的任务是从分发数据库中读取MSrepl_commands表里的命令并按顺序应用到订阅数据库的目标表上。它是数据流的“搬运工”也是最容易成为瓶颈的环节。快照代理Snapshot Agent驻留在分发器上只在初始化订阅或重新生成快照时运行。它负责生成发布对象的初始快照文件.bcp, .sch并将其复制到快照文件夹。虽然不常运行但其执行时间过长会直接影响新订阅的上线速度。实操心得这三个代理在 SSMS 的“SQL Server 代理”作业列表中都有对应条目命名规则为Publisher-Publication-Subscriber-AgentType。例如SQL01-SalesOrderPub-SQLREP-DISTRIBUTION就是分发代理的作业。永远不要手动禁用或删除这些作业否则复制会立即中断。正确的做法是通过复制监视器或 T-SQL (sp_helpsubscription) 来管理它们的状态。4.2 性能瓶颈诊断从“延迟”二字读懂系统健康在复制监视器中“延迟”Latency是最直观的健康指标。它显示的是一条在发布库中提交的事务到它在订阅库中成功应用所花费的时间。理想状态是 5 秒。如果延迟持续 30 秒就需要介入排查。我的诊断流程如下定位延迟源头在复制监视器中右键点击延迟高的订阅选择“查看监视器详细信息”。在弹出窗口中你会看到三个关键时间戳Last command time: 最后一条命令被写入分发库的时间。Current command time: 当前正在处理的命令在分发库中的时间戳。Delivery time: 该命令在订阅库中被应用的时间。 如果Last command time和Current command time相差很大比如几分钟说明日志读取器代理在“堵车”可能是发布库日志增长过快或者代理资源CPU/IO不足。 如果Current command time和Delivery time相差很大说明分发代理在“搬运”环节卡住了常见原因是订阅库上的目标表有阻塞如长时间运行的查询锁住了表、索引缺失导致 UPDATE/INSERT 慢、或网络带宽饱和。检查代理作业历史在 SSMS 中展开“SQL Server 代理” - “作业”找到对应的代理作业右键“查看历史记录”。重点看最近几次执行的“消息”列。常见的错误代码Error: 20084表示分发代理无法连接到订阅数据库。检查 SQLREP 的 SQL Server 服务是否运行、防火墙是否放行 1433 端口、Windows 域账户密码是否过期。Error: 2601违反唯一键约束。这通常意味着订阅库中存在与发布库不一致的脏数据或者筛选条件没写对导致同一条记录被多次插入。Error: 547违反外键约束。说明OrderDetails表里有一条记录其OrderID在Orders表中不存在。这往往是因为行筛选逻辑有漏洞或者在初始化快照后有人手动修改了订阅库的数据。分析分发数据库压力分发数据库的MSrepl_commands表是性能热点。如果该表的行数超过 100 万且SELECT COUNT(*) FROM MSrepl_commands查询很慢说明分发库需要维护。执行DBCC SHRINKFILE是下策正确做法是确保history_retention设置合理让旧的历史记录能被自动清理。定期运行sp_repldone仅在特殊情况下需 DBA 谨慎操作来标记已分发的事务。为MSrepl_commands表的xact_seqno列创建非聚集索引加速分发代理的读取。4.3 关键参数调优让代理跑得更快更稳调优不是“越大越好”而是要根据你的硬件和负载找到平衡点。以下是我在生产环境中反复验证过的最佳实践日志读取器代理的QueryTimeout默认是 30 秒。如果发布库日志文件非常大 50GB扫描一次可能超时。将其提高到 3005分钟可以避免代理因超时而重启但代价是单次扫描耗时更长。我的折中方案是保持 30 秒但确保发布库的日志文件被合理分割每个文件 8GB并通过DBCC LOGINFO检查 VLF虚拟日志文件数量将其控制在 100-200 个以内这样扫描效率最高。分发代理的MaxBcpThreads这个参数控制 BCP 导入时的并行线程数。默认是 1意味着所有命令串行应用。对于大表将其设为 CPU 核心数如 8可以显著提升吞吐量。但要注意过多的线程会加剧订阅库的 IO 压力可能导致其他业务查询变慢。我通常在业务低峰期将其设为 4高峰时回调到 1。分发代理的CommitBatchSize和CommitBatchThreshold这两个参数控制事务提交的粒度。CommitBatchSize设为 1000表示每处理 1000 条命令就提交一次事务CommitBatchThreshold设为 10000表示如果内存中累积了 10000 条命令即使没到 1000 条也强制提交。这样可以在“减少事务日志膨胀”和“保证数据一致性”之间取得平衡。我曾将CommitBatchSize设为 100000结果导致订阅库的事务日志在同步时暴涨 20GB差点把磁盘撑爆。独家技巧对于超大表 1 亿行的首次同步不要依赖自动快照。我通常会手动在订阅库上创建空表然后用bcp命令行工具配合-b 10000每批 10000 行和-h TABLOCK表级锁大幅提升导入速度参数直接从发布库导出再导入。整个过程比 SSMS 的快照代理快 5 倍以上且可控性极强。5. 常见问题与实战排障速查表那些年踩过的坑都帮你填平了再完美的设计在真实世界中也会遇到各种意想不到的状况。我整理了一份基于十年一线运维经验的“发布与订阅排障速查表”涵盖了从安装配置到日常运维中最高频、最棘手的 12 个问题。每一个问题都附带了我亲测有效的解决方案和背后的原理让你在深夜接到告警电话时能快速定位、精准出手。问题现象根本原因快速诊断命令终极解决方案我的血泪教训新建订阅后状态始终显示“未启动”且无法手动启动分发器未正确配置或分发数据库损坏EXEC sp_get_distributor_info(在发布者上执行)在分发器上重新运行sp_adddistributiondb并确保distribution数据库处于 ONLINE 状态。如果损坏从备份恢复或重建。曾因分发数据库的model文件损坏导致所有新订阅都无法创建。SSMS 图形界面只报“未知错误”用 T-SQL 才看到具体的File not found提示。复制监视器中延迟从 0 秒突然飙升到 300 秒以上且持续不降订阅库上目标表被一个长时间运行的查询如SELECT * FROM Orders WITH (NOLOCK)锁住SELECT blocking_session_id, wait_type, wait_time FROM sys.dm_exec_requests WHERE session_id 50 AND blocking_session_id 0找到阻塞会话KILL它。长期方案在订阅库上为所有复制表创建合适的索引特别是WHERE和JOIN条件列。一次大促期间BI 工程师跑了一个未加WHERE的全表扫描锁住了Orders表 12 分钟导致所有下游报表数据延迟。从此我们给订阅库加了严格的资源调控器Resource Governor。分发代理作业失败错误日志显示Could not find stored procedure sp_MSins_dboOrders订阅库中缺少复制所需的系统存储过程通常是因为手动删除了MSreplication_objects架构SELECT * FROM sys.objects WHERE name LIKE sp_MS% AND type P(在订阅库上执行)运行sp_addsubscription时确保sync_type Nautomatic让 SSMS 自动生成所有必要对象。如果已损坏删除并重建订阅。开发人员为了“清理垃圾”手动DROP SCHEMA MSreplication_objects结果所有复制存储过程都没了。重建订阅花了 4 小时损失惨重。快照代理运行缓慢生成一个 500MB 的快照文件耗时 3 小时发布库上表缺少合适的索引导致SELECT *扫描全表效率低下SET STATISTICS IO ON; SELECT * FROM Orders(在发布库上执行观察逻辑读次数)为发布表的主键或聚集索引列创建高效索引。对于超大表考虑在快照生成前用bcp手动导出再用bcp导入到订阅库。一个 2TB 的历史订单表快照生成要 18 小时。我们改用bcp -c -t, -S SQL01 -U user -P pass -d SalesDB -o orders.csv再导入全程 45 分钟。订阅库中数据与发布库不一致但复制监视器显示“正在运行”行筛选条件filter_clause编写有误或immediate_sync Ntrue导致初始快照包含了不该同步的数据SELECT COUNT(*) FROM [PublisherDB].[dbo].[Orders] WHERE Status CompletedSELECT COUNT(*) FROM [SubscriberDB].[dbo].[Orders]用sp_browsereplcmds查看分发库中待分发的命令确认是否包含错误的行。修正筛选条件后重新初始化订阅。一次筛选条件写成了Status Cancelled结果把所有“处理中”的订单都同步了导致下游库存系统误判。日志读取器代理频繁失败错误为The process could not execute sp_replcmds发布库的事务日志已满或sp_replcmds系统存储过程被意外修改DBCC OPENTRAN(查看是否有长时间未提交的事务)SELECT log_reuse_wait_desc FROM sys.databases WHERE name SalesDB清理长事务或对日志进行BACKUP LOG以释放空间。检查sp_replcmds是否被篡改必要时从另一台正常服务器上SCRIPT出来重建。一个未提交的BEGIN TRAN卡了 3 天日志无法截断sp_replcmds一直报错。DBCC OPENTRAN一下就定位到了。在订阅库上执行INSERT INTO Orders ...报错Cannot insert explicit value for identity column in table Orders when IDENTITY_INSERT is set to OFF订阅库的Orders表IDENTITY属性被复制但应用层试图显式插入OrderIDSELECT COLUMNPROPERTY(OBJECT_ID(Orders), OrderID, IsIdentity)在应用代码中移除对OrderID字段的显式赋值让数据库自动生成。或者在订阅库上SET IDENTITY_INSERT Orders ON但这违背了只读原则不推荐。应用团队不知道订阅库是只读的硬编码了OrderID。我们最终在应用层加了判断如果是订阅库环境则忽略OrderID字段。分发代理作业成功但订阅库中数据没有任何变化订阅库的MSrepl_originator_id系统表被清空或MSrepl_commands表中没有待处理命令SELECT COUNT(*) FROM distribution.dbo.MSrepl_commands WHERE xact_seqno (SELECT MAX(xact_seqno) FROM SalesRepDB.dbo.MSrepl_origin